Skip to content

Errors

The API uses conventional HTTP status codes and returns the same JSON envelope on every failure.

{
"error": {
"code": "not_found",
"message": "Content not found.",
"docs_url": "https://docs.nativpost.com/errors"
}
}

Everything is nested under a single error key — there is no top-level object: "error".

Fields:

  • code — the machine-readable identifier. Branch on this, never on message.
  • message — a human-readable summary suitable for logging and for showing to a developer.
  • docs_url — always this page.

The field is code, not type. There is no param field, no request_id, and no X-Request-Id response header.

StatuscodeMeaning
400invalid_bodyBody was not valid JSON, or failed schema validation.
400no_updatesA PATCH body contained no recognised updatable field.
401missing_keyNo Authorization header, or it did not carry an np_live_ key.
401invalid_keyKey not found, or revoked.
401expired_keyKey is past its expiry date.
402payment_requiredThe workspace subscription is inactive.
403plan_forbiddenThe workspace plan does not include API access.
404not_foundResource does not exist, or belongs to another workspace.
409invalid_stateState transition rejected — launching a campaign that is not draft or paused.
422no_target_platformsPublish was called on content with an empty target_platforms.
500internalUnexpected server error. Safe to retry after a short delay.

A revoked key is not distinguishable from a nonexistent one — both return invalid_key. This is deliberate.

When Zod validation fails, invalid_body carries an extra details object alongside the standard three fields. It is a Zod flatten() result:

{
"error": {
"code": "invalid_body",
"message": "Validation failed.",
"docs_url": "https://docs.nativpost.com/errors",
"details": {
"formErrors": [],
"fieldErrors": {
"caption": ["String must contain at least 1 character(s)"],
"content_type": ["Invalid enum value. Expected 'text_only' | 'single_image' | ..."]
}
}
}
}

fieldErrors is keyed by the request field name and each value is an array, because one field can fail several rules. formErrors collects errors that are not attributable to a single field.

details is present only on schema-validation failures. A malformed-JSON invalid_body has no details.

These three are distinct on purpose:

  • 401 is a key problem. The key is missing, wrong, revoked, or expired. Nothing the user does with billing will fix it — they need a valid key from Settings → API keys.
  • 402 payment_required means the workspace subscription is inactive. Nudge the user to update their payment method.
  • 403 plan_forbidden means billing is current but the plan does not include API access. Nudge the user to upgrade to Pro or higher.

The responses carry no upgrade_required or current_plan helper fields — branch on code and call GET /api/v1/me if you need the plan name to render a message.

The /api/v1 surface does not currently rate-limit, and does not emit 429. Plan limits are enforced on quota (posts per month, AI credits), not on request rate. Read the current limits from features on GET /api/v1/me.

500 internal is the only code worth retrying blind — back off and try again. Everything else is deterministic: the same request will fail the same way until you change it or change the workspace’s billing state.