Responses and errors

Every field of an answer, the error envelope with its types, and the headers on every response.

A successful answer

A check answers 200 with this JSON. Every plan gets every field.

FieldTypeDescription
statusstringAlways success.
workflowstringThe workflow the content was checked against.
workflow_versionintegerThe version of the workflow that decided.
summary.actionstringreject when a reject threshold is reached, else review when a review threshold is, else accept.
summary.reject_probnumberThe highest score among the rejected categories, 0 when none is.
summary.reject_reasonarrayWhy the content is rejected, highest score first.
summary.review_reasonarrayWhy the content goes to review, highest score first.
scoresobjectThe best score per category the workflow checks, from 0 to 1.
uncheckedarrayThe categories of the workflow this server could not check. Absent when every one was checked.
time_msintegerThe time spent checking, in milliseconds.
request_idstringThe id of this request, also in X-Request-Id and in the journal.
refstring or nullYour own id for the content, as you sent it.

A reason in reject_reason or review_reason has:

FieldDescription
idThe category id, such as nudity or pii_iban. It is stable: map it to your own messages.
textA short English title.
messageA full English sentence that can be shown to a user.
scoreThe score the category got.
thresholdThe threshold it reached.
sourceThe tool that produced the score.
Match on id, never on text or message. They are English defaults and may be reworded.

Errors

An error answers with the HTTP status and this envelope:

{
  "status": "failure",
  "error": {
    "type": "unsupported_media",
    "code": 415,
    "message": "The file is not a JPEG, PNG, WebP or GIF image."
  }
}

Switch on error.type: it is stable. error.code repeats the HTTP status, and error.message is English text for you, not for your users.

error.typeStatusWhen
unauthorized401The key is missing, malformed, invalid, revoked or expired.
forbidden403The key may not check this kind of content or use this workflow, or the request came from a browser.
organization_suspended403An operator suspended the organization. Write to contact@sway.events.
bad_request400An empty body, a text that is not JSON or has no text, a refused ref, or a form that lacks a field.
unknown_workflow400There is no workflow of that name.
corrupt_media400The file does not decode.
url_refused400The url of an image is not public, too large, too slow or not an image.
unsupported_media415Not a JPEG, PNG, WebP or GIF image, or a text sent without Content-Type: application/json.
body_too_large413A request body over 15 MB.
image_too_large413More than 50 megapixels (12 on the free plan), or a side over 16,384 pixels.
text_too_long413More than 20,000 characters (5,000 on the free plan).
rate_limited429Too many requests: wait Retry-After seconds.
too_many_concurrent429Too many checks at once: wait Retry-After seconds.
quota_exceeded429The free plan's included checks are used, or the spending cap is reached. See limits and quotas.
request_timeout408A SightEngine-compatible form took more than 10 seconds to arrive.
workflow_unavailable503The workflow has no rule for this kind of content, or none this server can check.
busy503The server is busy, or the share of it the free plans use is: retry after Retry-After.
internal500A bug on our side. Quote the request_id when you write to us.

What to do with an error

  • 400, 413, 415: the request is wrong or the content cannot be accepted. Do not retry; refuse the content or fix the call.
  • 401, 403: a configuration problem. Log it, without the key, and alert your team.
  • 408: send the form again once, from a faster connection if you can.
  • 429, 503 with Retry-After: wait that many seconds, and retry at most twice.
  • 500, a timeout or a network error: fail closed, as best practices explains.

Headers on every response

HeaderDescription
X-Request-IdThe id of the request. Quote it in a support message.
RateLimit-LimitThe burst size of your plan: how many requests the bucket holds.
RateLimit-RemainingHow many requests are left in the bucket.
RateLimit-ResetSeconds until the bucket is full again.
X-Quota-UsedChecks counted in the current period.
X-Quota-LimitChecks the period allows before quota_exceeded.
Retry-AfterSeconds to wait, on 429 and 503.