Error codes
MarkdownThe error codes returned by the endpoints and their meanings.
All errors are returned with a body in the same format:
{ "error": "refused", "detail": "origin_unverified: www.yourbrand.com" }error indicates the type of error and detail its specifics; both are codes. Some details contain a value after a colon (field_missing: customer_email). Do not show codes to visitors as they are; convert them into a text from your own translation files.
General
Section titled “General”| Status | Code | Meaning |
|---|---|---|
| 400 | invalid / <field> |
A field is missing or invalid. The detail names the field concerned: <field>_required, <field>_too_long, <field>_out_of_range. |
| 400 | invalid_json |
The body could not be read, or a text in the body contains a character that cannot be stored (NUL \u0000 or an unpaired surrogate) |
| 400 | invalid / invalid |
One of the query parameters contains a character that cannot be stored (NUL or an unpaired surrogate) |
| 401 | In the Admin API, no credentials or an invalid key; in the review writing flow, no token (token_required); in the Ingest API, an invalid server key (refused) or an invalid signature (signature_refused) |
|
| 403 | refused / … |
The request was recognised but not permitted; details below |
| 404 | not_found |
The record was not found or belongs to another brand |
| 405 | method_not_allowed |
The HTTP method is not supported; see the Allow header |
| 429 | rate_limited |
The rate limit was exceeded; for the values, see Limits |
Authentication and keys
Section titled “Authentication and keys”| Code | Meaning |
|---|---|
origin_missing |
The request does not include a domain (no Origin or Referer header) |
origin_unverified: <domain> |
The domain is not verified; the value is the domain in the request’s Origin (or, failing that, Referer) header |
unknown_site_key |
The site key is not recognised |
revoked_site_key |
The site key has been cancelled |
channel_inactive |
The channel is off |
unknown_key, malformed_key |
The server key is not recognised or its format is invalid |
revoked_key |
The server key has been cancelled |
tenant_inactive |
The service of the brand the key belongs to is frozen or closed (for a frozen brand, review reading and SSR requests keep working) |
scope_missing |
The key is not permitted to perform this operation |
unknown_channel |
The channel code is wrong or the channel is off |
server_only |
This endpoint can only be called with a server key |
Review list and SSR
Section titled “Review list and SSR”| Code | Meaning |
|---|---|
invalid / product |
The product code is missing or longer than 128 characters |
sort_not_allowed |
The sort order is not on the market’s permitted list |
invalid / rating |
The rating is outside 1–5 |
invalid / q_too_long, topic_too_long, topic |
The search expression or topic word is invalid |
invalid / style, style_too_long |
The layout in the SSR request is not cards or sidebar |
invalid / theme, theme_too_long |
The design in the SSR request is not warm, trust, editorial, minimal, soft, bold, natural or night |
refused / no_site_key |
The channel has no site key in use and no page of your own is linked for the channel’s method statement |
Signed-in customers
Section titled “Signed-in customers”The codes of the GET /v1/viewer and GET /v1/viewer/order calls. If these endpoints are called from the browser, 403 refused / server_only is returned.
| Code | Meaning |
|---|---|
invalid / customer |
The X-Sayproof-Customer header is missing, contains no @ or is longer than 254 characters |
invalid / order, order_too_long |
The order number is missing from the order page query or is longer than 128 characters |
Review writing flow
Section titled “Review writing flow”For these codes, the text shown to the visitor is determined by the script; you will not encounter them in headless use either.
| Code | Meaning |
|---|---|
token_required |
No writing token was sent |
token_refused / token_unknown, token_expired, token_spent, token_wrong_kind, token_foreign_tenant |
The token is not recognised, has expired, has already been used, is of the wrong kind or belongs to another brand |
token_refused / line_already_written |
A review has already been written for this order line |
token_refused / line_out_of_scope |
The order line is not within the scope of this token |
token_refused / token_foreign_market |
The link was opened on the site of a channel of a market other than the order’s |
invalid / rating_out_of_range |
The rating is invalid |
invalid / title_too_long: 200, body_too_long: 5000, author_too_long: 120 |
The text exceeds the permitted length |
invalid / aspect_unknown, aspect_out_of_range, aspect_required |
A rating topic is invalid or a required topic was left empty |
invalid / too_many_reviews |
More than 20 products were sent in a single call |
invalid / too_many_items: 6 |
More than 6 photos or videos were added to the review |
invalid / media_not_available |
The file was not uploaded for this order |
invalid / review_not_revisable |
The review is not in a state that can be corrected |
| Status | Code | Meaning |
|---|---|---|
| 400 | refused / type_not_allowed: <type> |
The file type is not supported |
| 400 | refused / too_large: <MB> |
The file size limit was exceeded |
| 400 | refused / too_many_items: 18 |
The number of files uploaded for this order and not yet added to a review reached the limit (18 per order line) |
| 422 | refused / video_too_long: 60 |
The video is longer than 60 seconds; the file was refused at upload |
| 413 | refused / too_large: <MB> |
The request body exceeds the permitted size |
| 422 | refused / quarantined |
The file was quarantined by the security scan |
| 422 | refused / image_unreadable |
The image could not be read or is not jpeg, png or webp |
| 422 | refused / image_too_large |
The image is over 40 megapixels |
| 422 | refused / video_unreadable, video_stream_missing |
The video could not be read or has no picture |
| 422 | refused / video_frame_too_large: 3840x2160 |
The video picture is larger than 3840×2160 (2160×3840 for a portrait video) |
| 422 | refused / video_frame_rate_too_high: 60 |
The video has more than 60 frames per second |
| 422 | refused / video_codec_not_allowed |
The video is not encoded with H.264, HEVC, MPEG-4, VP9 or AV1 |
| 422 | refused / scan_failed, processing_failed |
The file could not be scanned or processed |
The video limits are checked at upload when the server can read the video at that point. A video that cannot be read then is refused with the same limits while it is processed and is not shown on the site.
Reporting and voting
Section titled “Reporting and voting”| Code | Meaning |
|---|---|
invalid_reason |
The report reason is not on the list |
invalid / vote |
The vote is not up, down or none |
Questions and answers
Section titled “Questions and answers”| Code | Meaning |
|---|---|
refused / questions_disabled |
The brand has not turned on questions and answers (403) |
invalid / body_required, body_too_long, email_invalid, email_too_long, name_too_long, asker_too_long, product_required, product_too_long |
A missing or wrong field in the question or answer form |
token_required, token_refused / token_unknown, token_expired, token_wrong_kind, token_foreign_tenant, token_foreign_market |
The token sent with an answer is missing or not valid |
token_refused / not_a_buyer |
The token’s order does not contain the question’s product |
token_refused / asker_expired, asker_invalid |
The signed-in customer’s question token has expired, is not valid or belongs to another brand; the script turns the form into the form of a visitor who is not signed in |
not_found |
The question was not found, is not live or belongs to another market (404) |
answer_refused / text_steering, text_health_claim, text_personal_data, text_profanity, question_not_published |
Admin API: the brand answer steers, makes a health claim, contains personal information or swearing, or the question is not live (409) |
Transfer
Section titled “Transfer”| Status | Code | Meaning |
|---|---|---|
| 401 | refused / unknown_key, malformed_key, revoked_key |
The server key sent with Authorization: Bearer is not recognised, is malformed or has been cancelled |
| 403 | refused / scope_missing |
The server key does not have the send orders (orders) permission |
| 401 | signature_refused / signature_missing, timestamp_missing |
With signed sending, the signature or time header is missing |
| 401 | signature_refused / timestamp_invalid |
The time value could not be read or contains no time zone |
| 401 | signature_refused / timestamp_outside_window |
The time difference is more than ±5 minutes |
| 401 | signature_refused / signature_mismatch |
The signature does not match |
| 401 | signature_refused / secret_unknown |
The password ID is not recognised, the password has been cancelled or it cannot be used for this operation |
| 429 | rate_limited |
More than 60 requests from the same IP address were refused in 5 minutes because of the key, password or signature; for the values see Limits |
| 400 | invalid / body_invalid, body_empty |
The body is not valid JSON or is empty |
| 400 | invalid / batch_too_large: 100 |
More than 100 records were sent in a single request (the limit for email events is 500) |
| 400 | invalid / field_missing: <field>, field_invalid: <field>, field_too_long: <field> |
A field is missing, invalid or too long; a character that cannot be stored (NUL \u0000 or an unpaired surrogate) also makes the field invalid |
| 400 | invalid / market_required, market_without_policy: <market> |
The market could not be determined or no rules are defined for it |
| 400 | invalid / channel_unknown |
The channel code is wrong or the channel is off |
| 400 | invalid / market_channel_mismatch: <market> |
The order’s market is not the market of the channel the order is linked to |
| 400 | invalid / status_unmapped: … |
The state value is not recognised |
Admin API
Section titled “Admin API”The main codes returned by the panel and the Admin API. The panel turns these codes into sentences on screen.
| Status | Code | Meaning |
|---|---|---|
| 403 | forbidden / scope_missing |
The user’s role or the key’s permission is not sufficient for this operation |
| 403 | forbidden / brand_inactive |
The brand’s service is frozen or closed; in the panel only the data export can be prepared and downloaded |
| 403 | forbidden / panel_session_required, refused / panel_session_required |
The operation can only be done by a user signed in to the panel, not with an API key (order connection operations, the invitation email test send and the invitation delivery settings; on user, channel, key and personal data request operations a key receives forbidden / scope_missing) |
| 400 | invalid / scope_reserved: write |
Only the Sayproof team can grant the change settings (write) permission |
| 400 | invalid / username_taken |
The username is used by another brand; a user can belong to only one brand. An account that no longer belongs to any brand is added only if it is entered together with the email address registered on the account. |
| 400 | invalid / current_password |
The current password was wrong when changing the password |
| 400 | invalid / password_guessable, password_names_account |
The new password is easy to guess (a common word, team or name with a year or a run of digits) or contains Sayproof, the brand’s name or the username |
| 400 | invalid / email_required |
No email address was entered when adding a user, or a password link was requested for a user without an email address |
| 400 | invalid_passcode |
The verification code in two-step sign-in is wrong, expired or already used |
| 400 | second_step_expired |
More than 10 minutes passed after the password step, or there was no password step; signing in starts again |
| 409 | invalid / second_step_on |
Two-step sign-in is already on |
| 400 | invalid / address_needs_credentials |
The order connection address points to another server; the sign-in details have to be entered again too |
| 400 | invalid / cta_required |
The Write a review button section is required in an invitation email template |
| 400 | invalid / template_scope_taken |
There is already an invitation email template for this invitation type, language, market and channel; edit or remove that one first |
| 400 | invalid / channel |
The channel code does not exist in this brand (method statement, invitation email template, audit report and statistics requests) |
| 400 | invalid / verification_locked, sort_not_offered: <option>, default_sort_not_allowed, review_window_days_out_of_range, valid_from_past |
The market rules version could not be opened: proof of purchase required by the Sayproof rule cannot be turned off; the sort option is not in the Sayproof rule; the default sort is not among the options offered; the time to write is outside 7–365 days; the start date is in the past |
| 400 | invalid / channel_market_mismatch |
In an audit report, statistics or market rules request, the channel sent does not belong to the market sent |
| 400 | invalid / sender_domain_unverified |
The sender address’s domain is not verified |
| 400 | invalid / webhook_url_required, webhook_secret_missing |
If your own system sends invitations, a notification address and a notification address password are needed |
| 400 | invalid / addresses_required, addresses |
The opt-out lookup has no addresses, the list is invalid or it is longer than 1,000 addresses |
| 400 | invalid / url_invalid |
The address could not be read; enter a full https address |
| 400 | invalid / host_unverified |
The address given to the page check is not on one of your verified domains |
| 400 | invalid / review_style, review_theme |
The layout (cards, sidebar) or design (warm, trust, editorial, minimal, soft, bold, natural, night) sent for the channel is not recognised |
| 400 | identifier_mismatch |
The email address given when asking for the copy of a personal data access request does not match the request |
| 400 | invalid / base_url, remote_channel, <field>_required, address_needs_credentials |
For an order connection, the address uses a scheme other than https (if no scheme is written, https:// is added) or contains user details, a query string or # besides the domain; the Omnitron channel number is not made of digits; a required connection field is empty (for example username_required, client_secret_required, consumer_key_required); the address was changed to another domain without sending the sign-in details again |
| 400 | platform_refused / login_refused, credentials_missing, shop_address_invalid, api_credentials_missing, api_credentials_refused, api_scope_missing, host_unresolved, http_status: <code> … |
The platform could not be reached while trying an order connection; what the codes mean: Akinon, Shopify, WooCommerce |
| 409 | source_exists |
This channel already has an order or marketplace connection to this platform |
| 409 | source_inactive |
A read cannot be started for a paused order or marketplace connection |
| 400 | invalid / seller_id_locked, feed_url_required, feed_url_not_https, feed_url_not_applicable, file_required, column_missing: <column>, file_unreadable, unsupported_format, too_large, too_many_rows, reviews |
A missing or invalid field on a marketplace connection: a change of the seller number was tried, the address is not https, the file cannot be read or lacks a required column, no review to remove was found |
| 409 | source_has_reviews |
A marketplace connection with imported reviews live, or with the record of a review removed by a personal data request, cannot be removed |
| 409 | channel_inactive |
A read cannot be started for an order connection whose channel is off |
| 409 | channel_in_use |
The channel has reviews, orders or an order connection, so it cannot be removed; it can be turned off |
| 409 | channel_quota |
The channel allowance is full; no new channel can be created and no channel that is off can be turned back on. Send a request to the Sayproof team with Request another channel |
| 409 | channel_request_open |
A request for another channel is already waiting for an answer |
| 409 | refused / template_preset, template_default, template_assigned, template_retired |
The rating topic list could not be changed: a Sayproof preset list cannot be retired; the general list and a list assigned to a product type cannot be retired; no new version can be published for a retired list |
| 404 | not_found / export_not_ready |
The data export is not ready yet or its download period has ended |
| 409 | domain_in_statement / <channel code/language> |
The domain cannot be removed because the method statement address of the channel and language named in the detail uses it; change the address in Market rules › Method statement first |
| 400 | proof_refused / proof_not_found, token_mismatch, dns_value_mismatch, dns_unreachable |
The domain could not be verified: neither the verification file nor the TXT record was found; the file’s content is not the verification text; the TXT record was found but its value is different; there is no file and the domain’s DNS server could not be reached (try again shortly). The response also carries the path, dns_name and token fields. |
| 409 | domain_in_use / channels: <channel codes> |
The domain cannot be removed because the invitation landing page address of the channels named in the detail uses it; change the channel’s address first |
| 409 | seller_response_refused / text_personal_data, text_profanity, text_steering |
The seller reply was not saved: the text contains personal data (email, phone, ID, IBAN, card or address), abusive language or a sentence steering the customer into changing the review or rating |
| 409 | seller_response_refused / seller_response_open, parent_review_not_published |
The review already has a pending, live or hidden seller reply (withdraw it before writing a new one), or the review is not published |
| 409 | resolution_refused / text_personal_data, text_profanity, text_steering |
The resolution note was not saved; the reasons are the same as for a seller reply |
| 409 | resolution_refused / review_not_live |
A resolution note can only be added to a review that is published or hidden for now |
| 409 | statement_changed |
The method statement changed during confirmation; put the new text on your page and confirm again |
| 409 | sender_domain_refused / sender_domain_in_use |
The sender domain cannot be removed because the sender address on the Invitation sending screen or in a channel’s Sender email address field uses it |
| 409 | not_copyable |
The request is not a completed access request, so its copy cannot be downloaded again |
| 502 | sender_domain_check_failed / dns_unreachable |
The domain’s DNS server could not be reached; try again shortly |
| 502 | page_unreadable / … |
The page could not be read during the page check (for example http_status: 404) |
Product source reads
Section titled “Product source reads”These codes are not returned in an HTTP response; they are shown on the Reads tab of a source opened from the Products › Product sources tab as the reason a read failed.
| Code | Meaning |
|---|---|
page_limit_reached: <limit> |
The read reached the page limit before the catalogue ended; the next read continues from where it stopped |
cursor_repeated |
The source returned the same next-page cursor again; the read was stopped |
Example: converting an error code into a translation key
Section titled “Example: converting an error code into a translation key”The part of the detail value before the colon is the code itself. By combining it with the error value, you can build the key to look up in your own translation file. For example, the response {"error": "refused", "detail": "origin_unverified: www.yourbrand.com"} becomes the key sayproof.refused.origin_unverified.
export function sayproofErrorKey(problem) { const detail = String(problem?.detail ?? "").split(":")[0].trim(); const error = String(problem?.error ?? "unknown"); return detail ? `sayproof.${error}.${detail}` : `sayproof.${error}`;}
export async function sayproofProblem(answer) { const problem = await answer.json().catch(() => ({})); return { status: answer.status, key: sayproofErrorKey(problem), requestId: answer.headers.get("X-Request-Id") };}