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
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.

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.
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.