Documentation

API error reference

Last updated: August 4, 2026

Every non-2xx response from the Peerfold API is an RFC 7807 problem document, served as application/problem+json. Its type field is a URL into this page — one anchor per code, so a type of https://www.peerfold.com/developers/errors#plan_required lands on the plan_required section below.

The code field is the stable machine key. Branch on it. Do not branch on title or detail, which are prose and may be reworded, and do not match on type, whose host moves when the documentation moves. Codes are added over time; a code is never repurposed. Treat an unrecognized code as the status class it arrives with.

{
  "type": "https://www.peerfold.com/developers/errors#plan_required",
  "title": "Plan upgrade required",
  "status": 403,
  "code": "plan_required",
  "detail": "Your plan does not include API access.",
  "request_id": "req_01J…"
}

detail is present when there is something specific to say and absent otherwise. request_id is present on every error raised inside a request context; quote it in support tickets. Errors also carry the Peerfold-Version response header, and 429s carry Retry-After.

400 — the request

The request never reached the resource. Nothing changed on the server, and resending the same bytes fails the same way.

bad_request400

The request could not be read: unparseable JSON, a missing required parameter, or a value the endpoint cannot use at all.

Read detail, fix the request, and send it again. Do not retry it unchanged.

validation_failed400

The request parsed, but a field failed the endpoint's schema — wrong type, out of range, or an unknown enum value.

detail names what failed. Correct that field and resend.

idempotency_key_required400

The endpoint replays safely and therefore insists on it: an Idempotency-Key header is mandatory here and was not sent. Progress writes are the main case.

Resend with a unique Idempotency-Key. Reuse that same key for retries of the same write.

401 — the credential

The API could not establish who is calling. Both codes are about the credential itself, never about permissions.

unauthorized401

No credential was presented on an endpoint that requires one.

Send a secret key as Authorization: Bearer sk_… on the admin plane, or a learner JWT on the learner plane.

invalid_token401

A credential was presented and is not usable: an unknown or revoked secret key, or a learner JWT that is expired, malformed, or signed for another workspace.

Mint a fresh learner token, or check the key in Settings → Developers. Retrying with the same credential will not start working.

403 — permission, plan, and access rules

The caller is known. Three different reasons they still may not do this, kept apart because the fix for each is a different person's job.

forbidden403

The credential is valid but is not allowed to make this call — a missing key scope, the wrong plane, a test key on a live-only flow, or a workspace that requires SSO for this sign-in.

Use a credential that carries the scope or the plane the endpoint needs. This is a configuration fix, not a retry.

plan_required403

The workspace's plan or add-on does not include the capability the endpoint provides. API access, the lead-gen flows, the serverless relay and webhook redelivery are each gated this way.

Upgrade the workspace or buy the add-on. detail names the plan the call needs.

access_denied403

The learner is authenticated, and the content's access rules keep them out — for example, enrolling in a course they are not eligible for.

Grant the learner access (membership, organization, or a direct enrollment made by an admin), then retry.

404 — not visible

A 404 from this API means “not visible to you”, which is deliberately weaker than “does not exist”. Workspace scoping, private community cohorts, and workspaces without the community add-on all answer 404 rather than leak that something is there.

not_found404

No such resource is visible to this credential. The resource may not exist, may belong to another workspace, or may be one the caller is not permitted to know about.

Check the id and the workspace the key belongs to. Never treat a 404 as proof the record was deleted.

not_enrolled404

The enrollment addressed by the call does not belong to the calling learner, so there is no progress, quiz or exam record to read or write.

Enroll the learner first, then use the enrollment id the enrollment call returned.

409 — conflict with existing state

The request was well-formed and permitted; something already on the server contradicts it.

idempotency_conflict409

The Idempotency-Key has already been used for a request with a different body.

Use a new key for a genuinely new request. To get the original response back, resend the original body with the original key.

enrollment_protected409

The enrollment cannot be removed through the API because another system owns it — a purchase, a membership, or an org-level grant.

Remove it where it came from. detail names the source.

username_taken409

Community only. Another member of this workspace already holds the requested username.

Ask the member for a different one and resubmit.

slug_taken409

Community only. Another record in this workspace already uses the requested URL key.

Offer a different key and resubmit.

idea_cap_reached409

Community only. The member already holds as many OPEN ideas on that board as it allows. Shipped and declined ideas do not count against the limit.

Do not retry — a second attempt cannot succeed until a slot is freed. Move one of their ideas to shipped or declined, raise the board's limit, or give that member their own.

poll_locked409

Community only. The poll already has at least one vote, so its options are fixed — changing them would change what those voters chose.

Do not retry. A poll never becomes editable again once somebody has answered it; post a new thread with a new poll if the options were wrong.

lead_taken409

Partner programs only. That account cannot be registered by this partner right now: somebody else holds it and their protection window is still running, the window has lapsed but this partner's tier may not claim another partner's lead, the account already has a won deal on it, or the caller's own earlier registration has lapsed. The detail names who holds it and when the window clears.

Read the detail. A window that clears is a date to wait for; a lapsed registration of the caller's own is re-sent with confirmed: true; the other two cannot be retried into a success.

resource_in_use409

Something still points at the record you asked to remove, so removing it would orphan them.

Move whatever depends on it first. The detail says how many there are.

422 — the request is fine, the rules say no

Nothing about the request is wrong. A domain rule refuses it, and the caller can usually do something concrete about that.

gate_blocked422

A progression rule refused the completion: a required quiz not yet passed, minimum time on the lesson not elapsed, a video not watched, or a SCORM lesson that only its own package may complete.

Show detail to the learner — it states the condition. Do not retry until the condition is met.

username_required422

Community only. The action publishes a name and the member has not chosen one yet.

Send the member through the pick-a-username step, then retry the original action.

429 and 5xx — retry these

The only codes worth an automatic retry. The typed client marks exactly these as retryable.

rate_limited429

The caller exceeded the request budget for the workspace and key (admin plane) or the workspace and member (learner plane).

Wait for the Retry-After header, then retry with exponential backoff. X-RateLimit-* headers on every response show the remaining budget.

not_configured503

The endpoint depends on something that is not set up or not reachable right now — payments not connected for the store, the API disabled in this environment, or a CRM lookup that could not be completed.

If the workspace is missing the setup, finish it in the admin. Otherwise treat it as transient and retry with backoff.

internal_error500

Something failed on our side. The request may or may not have taken effect.

Retry with backoff, using the same Idempotency-Key if the endpoint takes one. If it persists, send us request_id.

Questions? Write to hello@peerfold.com and a person will answer.

Contact us