ohmywrap
Get an API key

§ Errors

Match on the code, never on the sentence.

Failures return application/problem+json with a stable machine-readable code and the request id that produced it. 85 of them, and the ones that are our fault are labelled as ours.

422 · application/problem+json
{
  "type": "about:blank",
  "title": "Unprocessable Entity",
  "status": 422,
  "code": "invalid_pdf",
  "detail": "input is not a readable pdf: ...",
  "requestId": "host/abc-000021"
}

code is the contract. detail is for a human reading a log and will be reworded without warning, so branch on the former. requestId is the same value as the X-Request-Id response header, and it is what we need if you ask us about a call.

What a failure costs

Nothing. Everything you can get wrong is knowable before we charge you, so those requests never reach the ledger at all. A failure after that point — a timeout, a fault of ours, even a client that hung up mid-job — is compensated with the credits put back. You pay for work we actually delivered.

The request itself

Unknown option keys are rejected rather than ignored, so a typo like {"rotaton":90} fails loudly instead of silently doing nothing.

Fault codes: The request itself
StatusCodeCause
400no_inputNo file part at all
400input_countToo few or too many file parts for the operation
400too_many_filesMore file parts than the operation’s ceiling
400unknown_fieldA form field the operation does not accept
400duplicate_fieldoptions, password, or an auxiliary field sent twice
400malformed_bodyThe multipart body could not be parsed
400invalid_requestA malformed request matching nothing more specific
404not_foundNo operation at that path
405method_not_allowedOperations are POST only
415unsupported_media_typeThe body was not multipart/form-data
422invalid_optionsMalformed JSON, or an option key the operation does not have

Ceilings and capacity

Admission is rationed by estimated memory rather than by request count, so many small jobs run at once while a few large ones do not. An exhausted budget is an immediate 503, not a queue that grows without bound.

Fault codes: Ceilings and capacity
StatusCodeCause
413file_too_largeOne part exceeds the operation’s per-file cap
413request_too_largeThe whole body exceeds the request cap
413field_too_largeoptions or password over its byte cap
413page_limit_exceededMore pages than the page ceiling
413split_limit_exceededA split that would produce more documents than the ceiling
413pixel_limit_exceededDecoded image pixels over the image caps
413extract_budget_exceededExtracted images over the count or byte ceiling
413job_too_largeEstimated memory exceeds the whole admission budget
503busyAdmission budget exhausted. Retry with jittered backoff, honouring Retry-After
504job_timeoutThe operation exceeded its time budget
500internal_errorAn unexpected fault. detail is withheld; quote the request id

pdf/*

Fault codes: pdf/*
StatusCodeCause
422invalid_pdfThe document could not be read
422password_requiredEncrypted, and the password is missing or wrong
422invalid_rotationRotation is zero, or not a multiple of 90
422invalid_spanspan below 1
422invalid_page_selectionMissing, malformed, or matching no page in the document
422invalid_stampContradictory or out-of-range stamp options
422invalid_imageAn input image could not be decoded
422invalid_import_configBad /pdf/from-images options
422invalid_pipelineNo steps, too many, or a step that cannot be chained
422not_encrypted/pdf/decrypt on a document with no protection
422already_encrypted/pdf/encrypt on an already-protected document
422no_images/pdf/extract-images found nothing on the selected pages
422new_password_required/pdf/change-password without a newPassword part
422no_metadata_change/pdf/set-metadata with nothing to add or remove

image/*

Fault codes: image/*
StatusCodeCause
415unsupported_formatA format outside the allowlist. The error names the ones that are in it
422invalid_imageThe image could not be decoded
422invalid_cropA crop rectangle that does not fit the image
422invalid_compositeContradictory gravity and offset options
422invalid_pipelineNo steps, too many, or a step that cannot be chained
413frame_limit_exceededAn animation with more frames than the ceiling
413output_too_largeThe requested output exceeds the output pixel cap

text/*

A detector that is advertised, accepted, and silently finds nothing is the worst failure these endpoints have, so an unrecognised name is refused and the error lists the seventeen that exist.

Fault codes: text/*
StatusCodeCause
415invalid_textThe input is not valid UTF-8
422unknown_detectordetect names something not on the allowlist
422no_detectorsdetect: [] with no terms — a scan that would find nothing
422invalid_strategystrategy is not one of the six
422invalid_redaction_formatredactionFormat contains %v, which puts the value back inside its own placeholder
422static_replacement_requiredstrategy is static with no staticReplacement
422invalid_mask_charactermaskCharacter is not exactly one character
422too_many_termsMore inline dictionary terms than the ceiling
422term_too_longA term over the per-term byte cap
422empty_termA term is blank
422option_not_applicableincludeMatches sent to /text/redact
422invalid_modemode is not token or surrogate
422invalid_token_formattokenFormat is blank or has no %n
422surrogate_unsupported_for_termsmode: "surrogate" with terms, which the engine has no generator for
409surrogate_collisionTwo values generated one stand-in. Retry in token mode, which cannot collide
409token_collisionA custom tokenFormat produced one token for two values
413too_many_tokensMore distinct values, or a larger mapping, than the ceiling
400missing_mapping/text/restore called with no mapping part
422invalid_mappingThe mapping part is not a JSON object of token to value
422empty_mappingThe mapping is empty
422invalid_tokenA mapping key is empty or over the byte limit
500redaction_failedThe engine failed on a policy this service built. Ours, not yours

office/*

Fault codes: office/*
StatusCodeCause
415unknown_input_formatThe extension is not an accepted input, or there is no extension
415invalid_textA text-shaped input is not valid UTF-8
422container_mismatchThe bytes do not match what the extension claims. Refused before a parser sees it
422missing_targetNo to
422no_conversionto is the format the input already is
422unsupported_targetThe pair is not in the matrix, pdf included. The error lists what this input can become
422conversion_failedLibreOffice produced nothing: corrupt, password-protected, or a filter that gave up
503office_busyEvery conversion slot is in use. Told to retry rather than queued behind your own deadline
504conversion_timeoutThe conversion did not finish in its budget
500office_engine_errorThe work directory or the process could not be set up. Ours, not yours

Your account

A database failure is deliberately not a 401: telling you the key is invalid because our console was briefly unreachable would send you to rotate a credential that was never the problem.

Fault codes: Your account
StatusCodeCause
401unauthenticatedNo Authorization: Bearer header
401invalid_api_keyNo active key matches: revoked, expired, or a suspended account
402insufficient_creditsThe pack is empty. Nothing written, nothing done
403operation_unavailableNo active price row: the operation is switched off or unknown
409idempotency_key_reusedThat Idempotency-Key has been used before
400invalid_idempotency_keyOver 120 characters, or not printable ASCII
429rate_limitedOver your account’s requests per second
429too_many_concurrent_requestsToo many of your requests in flight at once
429too_many_failed_keysThis address has presented too many unrecognised keys
503billing_unavailableThe console database is unreachable

Retrying

Every operation is a pure function of its inputs, so a retry is always safe. A 503 or a 429 is worth retrying with jittered backoff — both carry Retry-After, and on a rate refusal that figure is the real wait until your next token rather than a guess. A 4xx about the file itself will fail again the same way; fix the file.

A refused request never spends a rate token either, so a client that keeps retrying does not push its own next opening further out.

The ceilings behind the 413s

Your first call takes one line of curl.

No plan to choose. No credits to model. No storage to configure.

10,000 calls for $10. No subscription, no expiry.