Skip to content

Errors

Every error the External API v2 returns, from a malformed UUID to an internal failure, uses a single format: an RFC 9457 application/problem+json body with a stable, machine-readable code. There are no bare HTML error pages, no ad-hoc JSON shapes, and no format differences between authentication, validation, and business errors.

This page covers the envelope, the complete code catalog, field-level validation errors, and recommended handling per code. For the errors specific to posting instructions, see Instructions & instructability; for authentication setup, see Authentication.

Scope

This format applies to every route under /api/v2/external/.... Legacy v1 routes keep their historical error formats, which is one more reason to migrate.

The envelope

Every 4xx and 5xx response has Content-Type: application/problem+json and this shape:

MemberTypePresenceDescription
typestring (URI reference)alwaysStable URI reference identifying the problem type, e.g. /errors/not-found. Never a class name.
titlestringalwaysShort human-readable summary of the problem type. Stable per type, but display-only: do not switch on it.
statusintegeralwaysThe HTTP status code of this occurrence. Matches the response status line.
detailstringoptionalHuman-readable, occurrence-specific explanation. Free-form and localizable; never parse it.
instancestring (URI reference)optionalIdentifies this specific occurrence, typically the request path.
codestringalwaysStable machine-readable error code: the member to switch on in your integration. Full catalog below.
requestIdstring, nullablealwaysCorrelation id, mirrors the X-Request-Id response header. Quote it when contacting support.
errorsarrayoptionalField-level validation failures. Present on validation_failed; see Validation errors.

Some problem types add extension members at the top level of the body (RFC 9457 extensions): for example requiredScope on insufficient_scope, or clientCaseReference and missingPreviousRenewalDates on instruction_skipped. The catalog below lists every extension. Per RFC 9457, ignore any member you do not recognize: new extensions may be added without notice.

A complete example: a POST /patent-instructions rejected because an earlier renewal event of the same patent has no instruction yet.

json
{
  "type": "/errors/instruction-skipped",
  "title": "Instruction skipped",
  "status": 422,
  "detail": "An earlier renewal event of this patent is still awaiting an instruction. Instruct the earliest un-instructed event first.",
  "instance": "/api/v2/external/patent-instructions",
  "code": "instruction_skipped",
  "requestId": "7f3c2a9e-4b1d-4c6a-9e2f-8a5d0c3b1e47",
  "clientCaseReference": "ACME-EP-0042",
  "missingPreviousRenewalDates": ["2026-03-31"]
}

Here the extensions tell you which case is affected (clientCaseReference, your own docketing reference) and the due dates of the earlier events still awaiting a decision (missingPreviousRenewalDates). Why this rule exists, and how to avoid tripping it, is explained in Instructions & instructability.

Switch on code rather than on detail or title

code and type are contractual and stable. detail and title are human-readable text that may be reworded or localized at any time.

Error catalog

codeHTTPtypeWhen it happensExtensions
validation_failed400/errors/validation-failedA request body, query parameter, or path parameter failed validation: bad UUID or YYYY-MM path param, unknown sort field on GET /patents, missing due-date window on GET /patent-events, malformed instruction body…errors[]; see Validation errors
invalid_api_key401/errors/invalid-api-keyThe x-api-key header is missing or the key is unknown. See Authentication.None
api_key_expired401/errors/api-key-expiredThe key is recognized but past its expiresAt. Distinct from invalid_api_key so you can alert on rotation specifically.None
insufficient_scope403/errors/insufficient-scopeThe key is valid but does not carry the scope this endpoint requires (e.g. calling POST /patent-instructions with a read-only key).requiredScope: the missing scope, e.g. "patents:instructions:write"
forbidden403/errors/forbiddenBusiness-level denial unrelated to scopes, e.g. calling the exchange-rate-freeze endpoints when the FX-freeze capability is not enabled for your account. See Fees & FX.None
not_found404/errors/not-foundThe resource does not exist in your portfolio: an unknown id and someone else's id are indistinguishable by design. Also returned by document routes while the document does not exist yet.None
instruction_already_set409/errors/instruction-already-setThe event already has an executed decision. Instructions are executed immediately and are not modifiable via the API, so re-posting conflicts with the existing state.None
conflict409/errors/conflictAn externalImportRef sent to POST /patents/sync was already used with different items. The same reference with the same items would have replayed the stored batch instead. See Pushing your portfolio.existingBatchId: the batch already recorded under that reference
instruction_too_early422/errors/instruction-too-earlyThe instruction window has not opened yet (today is before the event's opensAt, the first day of the month two months before the due month).None
instruction_skipped422/errors/instruction-skippedAn earlier un-instructed event of the same patent must be instructed first: only the chronologically earliest un-instructed event of a patent can be instructed.clientCaseReference, missingPreviousRenewalDates
instruction_lapsed422/errors/instruction-lapsedThe requested decision is not offered for this event: typically PROCEED on a lapsed event, whose grace period is over and whose office no longer accepts payment. Check instructability.allowedDecisions.None
rate_limit_exceeded429/errors/rate-limit-exceededYour key's daily call quota is exhausted. The Retry-After header gives the seconds until the quota resets at midnight (server local time).None
internal_error500/errors/internal-errorUnexpected server-side failure. The detail is deliberately generic; the incident is logged server-side under your requestId.None

Instruction batches are atomic

A POST /patent-instructions call is all-or-nothing: if any entry fails with a 409 or 422, no instruction in the batch is executed. Fix or remove the offending entry and re-post. The reliable way to avoid these errors altogether is to read each event's instructability object first, because it is computed by the same rule the POST enforces. See Instructions & instructability.

401/403 semantics

invalid_api_key and api_key_expired are about the key itself; insufficient_scope is about what the key may do; forbidden is about what your account may do. Only the first two are fixable in your request configuration. The last two require a change on the Renewr side. See Authentication.

Validation errors

When code is validation_failed, the errors array pinpoints each failing field:

MemberTypeDescription
pointerstringRFC 6901 JSON Pointer to the offending field in the request body, e.g. /instructions/0/eventId. ~ and / in key names are escaped as ~0 and ~1.
codestringStable machine-readable code for this specific field failure.
messagestringHuman-readable, localizable description. Display it, do not parse it.

Example of a bad instruction body:

bash
curl -X POST "https://api.renewr.example/api/v2/external/patent-instructions" \
  -H "x-api-key: $RENEWR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "instructions": [
      { "eventId": "not-a-uuid", "decision": "PROCEED" },
      { "eventId": "3f8a1c2e-9b4d-4e7a-8c1f-2d5e6a7b8c9d", "decision": "RENEW" }
    ]
  }'
json
{
  "type": "/errors/validation-failed",
  "title": "Validation failed",
  "status": 400,
  "detail": "The request failed validation.",
  "instance": "/api/v2/external/patent-instructions",
  "code": "validation_failed",
  "requestId": "b2d94c6a-1e3f-4a8b-9c7d-5e2f1a0b3c4d",
  "errors": [
    {
      "pointer": "/instructions/0/eventId",
      "code": "invalid_string",
      "message": "Invalid uuid"
    },
    {
      "pointer": "/instructions/1/decision",
      "code": "invalid_enum_value",
      "message": "Invalid enum value. Expected 'PROCEED' | 'SKIP_THIS_TIME' | 'DROP', received 'RENEW'"
    }
  ]
}

The same envelope covers invalid query and path parameters: a non-UUID patentId on GET /patents/{patentId}, a malformed yearMonth on PUT /exchange-rate-freezes/{yearMonth}, or an incomplete due-date window on GET /patent-events all return a standard validation_failed 400.

Not everything invalid is an error

Some inputs are deliberately forgiving instead of failing: an unknown include value is silently ignored, and itemsPerPage above 100 is silently clamped to 100. See Conventions for the full list of lenient behaviors.

Sync items fail as outcomes, never as HTTP errors

POST /patents/sync validates per item: a malformed item is reported as outcome: "REJECTED" with a reason, inside a successful 201/202 response, and the other items proceed. A 400 validation_failed on that endpoint only ever concerns the envelope itself: an empty items array, an entry of items that is not a JSON object (null, a bare string or number 400s the whole request), an empty externalImportRef, or malformed JSON. Error handling for a push therefore lives in the per-item results, and this catalog only covers its envelope; see Pushing your portfolio.

Handling guidance

Three failure classes: retry it, fix it, or read it as a state signal.

codeClassWhat your integration should do
rate_limit_exceeded (429)RetryWait the Retry-After seconds, then resume. Do not hammer: the quota is daily. Spread batch work to stay under it; see Syncing data.
internal_error (500)RetryRetry with exponential backoff (e.g. 1 s, 2 s, 4 s, a few attempts). If it persists, stop and contact support with the requestId.
validation_failed (400)FixA bug in your request construction. Log the errors[] pointers, fix the code, do not retry unchanged.
invalid_api_key (401)FixCheck the x-api-key header and key value. See Authentication.
api_key_expired (401)FixThe key needs rotation. Alert your operations channel; no request change will help.
insufficient_scope (403)FixThe key lacks requiredScope. Request a key with the right scopes.
forbidden (403)FixCapability not enabled for your account (e.g. FX freeze). Contact Renewr, do not retry.
instruction_too_early, instruction_skipped, instruction_lapsed (422)FixA business-rule violation. Consult the event's instructability (opensAt, blockedReason, allowedDecisions) and adjust; see Instructions & instructability.
instruction_already_set (409)State signalThe decision is already recorded, which is usually a success from your workflow's point of view. Common on replays after a timeout: treat it as "already done", verify via GET .../events/{eventId} (appliedDecision), and continue.
conflict (409)FixYour externalImportRef generation reused a reference for different content. Mint a new reference for the new content; use existingBatchId to inspect what the old reference covered. Do not mutate items under a reused reference.
not_found (404)State signalThe resource is not in your portfolio. If you expected it, check the id and whether the patent was imported; see Syncing data. Never retry in a loop.

429 responses omit the rate-limit headers

Successful authenticated responses carry RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset. Use them to throttle proactively. The 429 itself does not carry them; it carries Retry-After instead. Details in Conventions.

Correlating with support: requestId

Every response, success or error, carries an X-Request-Id header, and every problem body mirrors it in requestId. Server-side, 5xx incidents are logged under that same id.

  • Log it with every failed call in your integration.
  • Quote it in any support request: it lets Renewr find the exact server-side trace of your call.
  • Optionally set it yourself: if you send a non-empty X-Request-Id request header, it is echoed back instead of a server-generated one, letting you propagate your own correlation ids end-to-end.
bash
curl -i "https://api.renewr.example/api/v2/external/patents/3f8a1c2e-9b4d-4e7a-8c1f-2d5e6a7b8c9d" \
  -H "x-api-key: $RENEWR_API_KEY" \
  -H "X-Request-Id: my-sync-run-2026-07-22-0042"

Next: the routine errors you will actually meet in production show up while polling; see Syncing data and the monthly renewal cycle for where each one fits in a real integration.

Renewr External API v2. Access on invitation.