REST API
API Error Codes
Updated
Every error code the Tesselith REST API returns, what it means and what to do about it, with retry rules and an example handler.
When a Tesselith API request fails, the response is an RFC 9457 problem document with Content-Type: application/problem+json. Its code is stable and meant for your code to switch on. Its type is a link to that code's entry on this page. Its detail is a sentence for humans and can change wording at any time.
{
"type": "https://tesselith.com/docs/api/errors#no-queue-slot",
"title": "Unprocessable content",
"status": 422,
"detail": "These channels have no free queue slot in common. Add times to their posting schedule, or pick a time.",
"code": "NO_QUEUE_SLOT"
}Handling errors in code
const res = await fetch(`${API}/posts/${id}/queue`, { method: "POST", headers, body: "{}" })
if (!res.ok) {
const problem = await res.json()
switch (problem.code) {
case "NO_QUEUE_SLOT":
return scheduleAt(id, fallbackTime) // no slot: pick a time instead
case "RATE_LIMITED":
await sleep(Number(res.headers.get("Retry-After")) * 1000)
return queue(id) // safe to retry
case "VALIDATION_ERROR":
throw new Error(problem.errors.map((e) => `${e.path}: ${e.message}`).join("; "))
default:
throw new Error(`${problem.code}: ${problem.detail}`)
}
}Authentication and access
UNAUTHORIZED
HTTP 401. No Authorization: Bearer header, or the key is wrong, expired or revoked, or its creator is no longer in the workspace.
What to do: Check the key in Settings → Developers. Don't retry automatically: the same key will fail the same way.
FORBIDDEN
HTTP 403. The key is valid but not allowed to do this. Either it lacks the scope (the response's required_permission names it), or its creator's current role doesn't include that permission.
What to do: Create a key with the missing scope, or ask an owner to raise the creator's role. A key never has more access than its creator.
POST_LIMIT_REACHED
HTTP 403. Scheduling this post would go past the plan's monthly post allowance. Each channel a post goes to counts as one, and scheduled posts count as soon as they're scheduled.
What to do: Wait for the next billing period or upgrade the plan. Drafts don't count, so you can keep writing them.
PLAN_UPGRADE_REQUIRED
HTTP 403. The workspace's plan doesn't include this feature. The API, webhooks and MCP are included from the Pro plan; the response's required_plan names the lowest plan that has it.
What to do: Upgrade the workspace under Plans & billing. Existing keys start working again as soon as the plan changes.
Requests
VALIDATION_ERROR
HTTP 422. The body or query didn't match the schema. The errors array lists each problem with a dotted path (for example thread.0.channel_id) and a message.
What to do: Fix the listed fields. The OpenAPI document at /v1/openapi.json describes every field and its limits.
BAD_REQUEST
HTTP 400. The request couldn't be read at all, usually malformed JSON or the wrong Content-Type.
What to do: Send Content-Type: application/json with a valid JSON body (or multipart for file uploads).
NOT_FOUND
HTTP 404. The post, channel, endpoint or route doesn't exist in this workspace. Objects in other workspaces are reported as not found, never as forbidden.
What to do: Check the id and that the key belongs to the workspace that owns the object.
INTERNAL_ERROR
HTTP 500. Something failed on our side. It has been logged.
What to do: Retry with backoff and the same Idempotency-Key. If it persists, email [email protected] with the time of the request.
Posts and scheduling
UNKNOWN_CHANNEL
HTTP 422. One or more channel_ids (or a thread item's channel_id) aren't channels in this workspace.
What to do: Use ids from GET /channels.
UNKNOWN_MEDIA
HTTP 422. One or more media_ids aren't media files in this workspace.
What to do: Upload with POST /media first and use the returned id.
INVALID_TRANSITION
HTTP 422. The post can't move to that status from where it is, for example editing a post that is already publishing or has been published.
What to do: Read the post's current status and act on that. Create a new post to repost published content.
SCHEDULE_TIME_REQUIRED
HTTP 422. A post was set to scheduled without a time.
What to do: Use POST /posts/{id}/schedule with scheduled_at, or POST /posts/{id}/queue to take a slot.
NO_CHANNELS
HTTP 422. The post has no channels, so there is nothing to queue it on.
What to do: Set channel_ids with PATCH /posts/{id} first.
NO_QUEUE_SLOT
HTTP 422. The post's channels share no free posting slot in the planning window. Either a channel has no weekly schedule, or every slot is taken.
What to do: Add times with PUT /channels/{id}/schedule, or schedule for an exact time instead.
QUEUE_CHANGED
HTTP 409. The queue was changed by someone else while a reorder was being saved.
What to do: Fetch the queue again and retry.
APPROVAL_DISABLED
HTTP 400. The post was sent for client approval in a workspace that hasn't turned approvals on.
What to do: Enable approvals in Settings → Approvals, or schedule the post directly.
Media
UNSUPPORTED_MEDIA_TYPE
HTTP 422. The file's type isn't accepted. Allowed: JPEG, PNG, GIF, WebP, MP4, MOV, WebM, AVI, WMV, MPEG, 3GP, FLV, PDF, VTT, SRT and TTML.
What to do: Convert the file, and make sure multipart uploads send the right Content-Type for the part.
FILE_TOO_LARGE
HTTP 422. The file is over the limit for its type: 32 MB for images, 100 MB for everything else through the API. max_bytes gives the exact limit.
What to do: Compress the file or upload a smaller rendition.
URL_UNAVAILABLE
HTTP 422. A media URL couldn't be fetched safely: it isn't public HTTPS, resolves to a private or metadata address, redirects, or took longer than 15 seconds.
What to do: Host the file at a public HTTPS URL that serves it directly, or upload the bytes as multipart.
STORAGE_UNAVAILABLE
HTTP 503. Media storage is temporarily unavailable.
What to do: Retry with backoff.
Webhooks
INVALID_URL
HTTP 422. The endpoint URL isn't allowed: it must be public HTTPS, and private, loopback, link-local and cloud-metadata addresses are refused.
What to do: Use a publicly reachable HTTPS URL. For local testing, expose your server with a tunnel.
LIMIT_REACHED
HTTP 409. The workspace already has 20 webhook endpoints.
What to do: Delete unused endpoints, or subscribe one endpoint to several event types.
Retries, idempotency and limits
RATE_LIMITED
HTTP 429. The key went over its plan's requests per minute: 120 on Pro, 300 on Team, 600 on Agency.
What to do: Wait the number of seconds in Retry-After. The RateLimit header on every response tells you how many requests remain.
BAD_IDEMPOTENCY_KEY
HTTP 400. The Idempotency-Key header is empty or longer than 255 characters.
What to do: Send a UUID or your own record id.
IDEMPOTENCY_IN_PROGRESS
HTTP 409. A request with the same Idempotency-Key is still being processed.
What to do: Retry after a short delay; you'll get the original response once it finishes.
IDEMPOTENCY_KEY_REUSED
HTTP 422. The Idempotency-Key was already used in the last 24 hours for a request with a different method, path or body.
What to do: Use a new key for each distinct request. Reuse a key only to retry the identical request.
Questions developers ask
- Which API errors are safe to retry?
- 409 IDEMPOTENCY_IN_PROGRESS, 429 RATE_LIMITED (after Retry-After), 500 INTERNAL_ERROR and 503 STORAGE_UNAVAILABLE. Retry with the same Idempotency-Key so a request that actually succeeded isn't repeated.
- Why do I get NOT_FOUND for an object I know exists?
- The key belongs to a different workspace. Objects outside the key's workspace are always reported as not found, so the API never confirms they exist.