Errors
The API uses conventional HTTP status codes and returns the same JSON envelope on every failure.
Error envelope
Section titled “Error envelope”{ "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 onmessage.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.
| Status | code | Meaning |
|---|---|---|
| 400 | invalid_body | Body was not valid JSON, or failed schema validation. |
| 400 | no_updates | A PATCH body contained no recognised updatable field. |
| 401 | missing_key | No Authorization header, or it did not carry an np_live_ key. |
| 401 | invalid_key | Key not found, or revoked. |
| 401 | expired_key | Key is past its expiry date. |
| 402 | payment_required | The workspace subscription is inactive. |
| 403 | plan_forbidden | The workspace plan does not include API access. |
| 404 | not_found | Resource does not exist, or belongs to another workspace. |
| 409 | invalid_state | State transition rejected — launching a campaign that is not draft or paused. |
| 422 | no_target_platforms | Publish was called on content with an empty target_platforms. |
| 500 | internal | Unexpected 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.
Validation errors
Section titled “Validation errors”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.
Handling 401, 402, and 403
Section titled “Handling 401, 402, and 403”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_requiredmeans the workspace subscription is inactive. Nudge the user to update their payment method. - 403
plan_forbiddenmeans 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.
Rate limiting
Section titled “Rate limiting”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.
Retrying
Section titled “Retrying”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.