Skip to the page
Sections

Outcomes

Every reply a frontend can meet, in one table, and the rule that separates a consumer's outcome from an error in your request.

  • About the consumer's code, phone, receipt or the campaign window: HTTP 200 with a submission, a message_key and a ledger row.
  • About the caller's request (the key, the shape, a missing upload, a file's type or size): a 4xx or 5xx with an error.code and no ledger row.
  • 429 rate_limited is only for caller flood limits: a key over its request ceiling, or too many lookups or code checks. A consumer's daily limit is a 200 with daily_limit.

Row written is whether the request adds a row to the submissions ledger. Retryable, for an error, means sending the same request again (with the same Idempotency-Key where there is one) can succeed without changing it; for a consumer outcome it means the same code and phone can be submitted again later and get a different result.

About the consumer: HTTP 200

About the consumer's code, phone, receipt or the campaign window: HTTP 200 with a submission, a message_key and a ledger row.

ConditionHTTPstateReview statusmessage_keyerror.codeRow writtenRetryable
The code is good and the campaign needs nothing else200acceptednoneenterednoneYesNo
The code is good and carries an instant prizeinstant_win names the prize.200acceptednoneinstant_winnoneYesNo
The code is good and TRA's record of the receipt passes every ruleentries is the number of qualifying items on TRA's record.200acceptedverifiedenterednoneYesNo
The code is being checked and has not settled yetPoll GET /api/v1/submissions/{id} as poll_after_seconds says. The sentinel TEST-0-SETTLES does this.200pendingnonecheckingnoneYesNo
The upstream did not answer, or answered with something unrecognisedShown to the consumer as "Checking". Poll; do not submit again: a retry returns this same row.200unknownnonecheckingnoneYesNo
The receipt is recorded and waiting for TRA, or for a personTRA did not answer in time, the code is cooling down, a typed receipt needs a second look, or a line on the receipt matches no product. Tell the consumer the page can be closed.200pendingunder_reviewreceipt_receivednoneYesNo
The receipt submission is recorded and its one TRA lookup is runningSeen only by a second request while the first is still in flight and older than the time budget, or on GET. It settles within seconds.200pendingunder_reviewcheckingnoneYesNo
The QR code on the receipt photo could not be readOffer the typed path (a new receipt and a new submission); otherwise a person reviews the photo.200pendingunder_reviewreceipt_unreadablenoneYesNo
The code is not recognisedAlso a code from a live batch sent with a test key, and the reverse.200rejectednoneinvalidnoneYesNo
The code was already used by an entry that counted200rejectednonealready_usednoneYesNo
The code is attached to another entry that is still being checkedThe code is released if that entry ends rejected.200rejectednonecode_heldnoneYesYes
The phone has reached the campaign's daily limitTomorrow, in the campaign's timezone.200rejectednonedaily_limitnoneYesYes
The phone has reached the campaign's total limit200rejectednoneentry_limitnoneYesNo
The campaign needs an in-store code and none was sentNative campaigns. With the code from the in-store QR.200rejectednonecode_requirednoneYesYes
The phone is blocked from enteringSeebait campaigns. Today also a receipt a reviewer declines for the phone's cap: the review path uses blocked where the submit path uses entry_limit.200rejectednoneblockednoneYesNo
The campaign's window has not openedCheck status on GET /api/v1/campaign before showing the form.200rejectednonecampaign_not_startednoneYesYes
The campaign's window has closed200rejectednonecampaign_endednoneYesNo
The campaign is pausedOps paused it; entries resume when it is set live again.200rejectednonecampaign_pausednoneYesYes
The receipt is not from a participating store200rejecteddeclinedreceipt_wrong_storenoneYesNo
The receipt is dated outside the promotion200rejecteddeclinedreceipt_outside_windownoneYesNo
No participating product is on the receipt200rejecteddeclinedreceipt_no_qualifying_productnoneYesNo
The receipt has already been entered200rejecteddeclinedreceipt_already_usednoneYesNo
TRA has no receipt with that codeWith a corrected code, or a clearer photo, as a new receipt and a new submission.200rejecteddeclinedreceipt_not_foundnoneYesYes
The typed time does not match TRA's recordWith the time exactly as printed, as a new receipt and a new submission.200rejecteddeclinedreceipt_time_mismatchnoneYesYes
The phone's entries from receipts have reached the campaign's cap200rejecteddeclinedentry_limitnoneYesNo
The same Idempotency-Key and the same body were sent beforeThe stored submission comes back as it is now, with the header Idempotent-Replayed: true.200as storedas storedas storednoneNoYes
A new Idempotency-Key, but the same campaign, code and phone as an earlier submission that is pending, unknown or acceptedThe earlier submission comes back with replayed: true. After a rejected one, a new submission is made.200as storedas storedas storednoneNoYes

About your request: an error

About the caller's request (the key, the shape, a missing upload, a file's type or size): a 4xx or 5xx with an error.code and no ledger row.

ConditionHTTPstateReview statusmessage_keyerror.codeRow writtenRetryable
No Authorization header was sent401nonenonenonekey_missingNoNo
The header is not Bearer <key>, or the key is not in the utc_test_… / utc_live_… format401nonenonenonekey_malformedNoNo
The key is well formed but is not one of ours401nonenonenonekey_unknownNoNo
The key has been revoked403nonenonenonekey_revokedNoNo
A live key was used on a deployment that is not productionTest keys work on every deployment, production included.401nonenonenonekey_wrong_environmentNoNo
A UTC-Campaign header names a campaign other than the key's422nonenonenonecampaign_mismatchNoNo
A BA agent's session has expirednot yet returnedBA sessions arrive with the BA portal cutover.401nonenonenonesession_expiredNoNo
A BA agent named a campaign they are not assigned tonot yet returnedBA sessions arrive with the BA portal cutover.403nonenonenonecampaign_not_assignedNoNo
The body is not JSON, or a field is missing or has the wrong shapeerror.field names the first problem. Also: no code where the campaign needs one; a receipt for a campaign that takes none.422nonenonenonevalidation_failedNoNo
The phone is not a Tanzanian mobile numberValidate in the form first; this is the caller's request, so no row is written.422nonenonenonephone_invalidNoNo
consent is missing or not true422nonenonenoneconsent_requiredNoNo
POST /api/v1/submissions was sent without an Idempotency-Key422nonenonenoneidempotency_key_requiredNoNo
The Idempotency-Key is shorter than 16 or longer than 128 characters422nonenonenoneidempotency_key_invalidNoNo
The Idempotency-Key was used before with a different body422nonenonenoneidempotency_key_reusedNoNo
A receipt submission with this Idempotency-Key is still being processedWait Retry-After seconds. Past the time budget the stored pending submission is returned instead.409nonenonenoneidempotency_in_progressNoYes
A TEST-0-… code or TEST0… receipt was sent with a live key422nonenonenonesentinel_with_live_keyNoNo
The id is not a uuid, or names nothing in this key's campaign and environmentAlso a receipt_id that does not exist, and a key whose campaign is not in the database.404nonenonenonenot_foundNoNo
The campaign needs a receipt and the submission has no receipt_id422nonenonenonereceipt_requiredNoNo
The receipt's photo is not in storageAfter the PUT succeeds, with the same Idempotency-Key.422nonenonenonereceipt_not_uploadedNoYes
The receipt already belongs to a submission422nonenonenonereceipt_already_attachedNoNo
A typed receipt was sent to a campaign that takes no receipts422nonenonenonetyped_receipt_unsupportedNoNo
size_bytes is over 5 MB, or the stored file is413nonenonenonefile_too_largeNoNo
The stored file is not a JPEG, PNG or WebPA wrong content_type on POST /api/v1/receipts is 422 validation_failed; this is the file itself, found at submission.415nonenonenonefile_wrong_typeNoNo
The key is over its requests-per-minute ceiling, or the lookup or code-check limit was reachedAfter Retry-After seconds.429nonenonenonerate_limitedNoYes
Something failed on our sideWith the same Idempotency-Key. Quote the request id if it keeps failing.500nonenonenoneinternal_errorNoYes
The submission could not be recordednot yet returnedReserved: no call answers with it today.503nonenonenoneledger_unavailableNoYes

Copy for your page

Five message keys are never the message_key of a submission. They are copy for the frontend to show when the API answers with the matching error, or when the page's own check fails.

ConditionHTTPstateReview statusmessage_keyerror.codeRow writtenRetryable
Show when the API answers 422 receipt_not_uploadedThe copy is in messages on GET /api/v1/campaign.nonenonenonereceipt_not_uploadednoneNoYes
Show when the chosen photo is over the limit, or the API answers 413Check the size in the browser before asking for an upload URL.nonenonenonefile_too_largenoneNoYes
Show when the chosen file is not a JPEG, PNG or WebP, or the API answers 415Check the type in the browser first.nonenonenonefile_wrong_typenoneNoYes
Show when the API answers 429Wait Retry-After seconds before letting the consumer try again.nonenonenonerate_limitednoneNoYes
Show when the API cannot be reached or answers 5xx after the client's retriesKeep the form's values and the Idempotency-Key so the consumer can press submit again.nonenonenoneservice_unavailablenoneNoYes