Account
Errors
The error response format, every API error code, and the codes recorded on failed targets.
Relaycast uses conventional HTTP status codes and returns a JSON error body with a machine-readable code. There are two kinds of errors to handle:
- Request errors are returned immediately by the API, before anything is created.
- Publish errors happen later, in the background, and are recorded on individual targets of an accepted post.
Error format
{
"error": {
"code": "validation_error",
"message": "Content is not valid for some platforms",
"details": {
"2b7d9e1f-3a4c-4d5e-8f6a-7b8c9d0e1f2a": [
{ "field": "content", "message": "Bluesky allows 300 characters (this post has 342)" }
]
}
}
}| Field | Description |
|---|---|
code | Stable identifier. Branch on this. |
message | Human-readable explanation. It may change, so don't parse it. |
details | Optional extra data. Its shape depends on the code. |
Every response also has an X-Request-Id header. Log it with the error to find the call in Request Logs.
HTTP status codes
| Status | Meaning |
|---|---|
200 | OK. Also returned when an Idempotency-Key replays an existing post. |
201 | Created: a post was accepted, or a Bluesky account connected. |
400 | The request is invalid. Fix it before retrying. |
401 | Missing or invalid access key. |
402 | Not enough credits. |
404 | The post or route doesn't exist (or belongs to another project). |
413 | The request body is larger than 1 MB. |
429 | Rate limit exceeded. |
500 | Unexpected server error. Safe to retry with backoff. |
Request error codes
| Code | Status | When | details |
|---|---|---|---|
invalid_json | 400 | The body isn't valid JSON. | |
validation_error | 400 | The body doesn't match the schema, or content, media or options aren't valid for a platform, or scheduledFor is in the past. | Schema problems: an array of issues with path and message. Platform problems: an object keyed by account ID, each with { field, message } items. |
unknown_account | 400 | An accountId isn't in your project. | { "missing": [ids] } |
account_inactive | 400 | An account needs reconnecting. | { "accountIds": [ids] } |
unknown_media | 400 | A media:// reference isn't in your media library, in media or in options. | { "missing": ["media://..."] } |
invalid_credentials | 400 | Bluesky rejected the identifier or app password. | |
unauthorized | 401 | No access key, or the key is unknown or revoked. | |
insufficient_credits | 402 | The project doesn't have enough credits for every target. | |
not_found | 404 | Unknown post ID or route. | |
rate_limited | 429 | More requests than the per-minute limit for this key. | |
internal_error | 500 | Something went wrong on our side. |
Request bodies over 1 MB are rejected with 413 and the code payload_too_large, in the same JSON format.
Schema validation details
When the body itself is malformed, details lists the problems, each with a path into the body:
{
"error": {
"code": "validation_error",
"message": "Invalid request body",
"details": [
{ "code": "invalid_type", "path": ["targets", 0, "accountId"], "message": "Invalid UUID" },
{ "code": "custom", "path": ["scheduledFor"], "message": "Either publishNow or scheduledFor is required" }
]
}
}Platform validation details
When the body is well-formed but doesn't fit a platform, details is keyed by account ID, so you can show problems next to the right account:
const res = await fetch("https://social-publish-api.onrender.com/v1/posts", { method: "POST", headers, body });
if (res.status === 400) {
const { error } = await res.json();
if (error.code === "validation_error" && error.details && !Array.isArray(error.details)) {
for (const [accountId, issues] of Object.entries(error.details as Record<string, { field: string; message: string }[]>)) {
for (const issue of issues) console.warn(`${accountId} ${issue.field}: ${issue.message}`);
}
}
}Publish errors
Once a post is accepted, each target publishes on its own. A target that fails has status: "failed" with errorCode and errorMessage set. Its credit is refunded.
errorCode | Meaning | Retried automatically |
|---|---|---|
media_error | The media couldn't be downloaded, was too large, or was rejected by the platform. Also used when a media:// file was deleted before publishing. | Only for temporary download failures (5xx or network) |
invalid_content | The platform rejected the content, for example a duplicate post or a format it won't accept. | No |
auth_revoked | The platform no longer accepts the account's credentials. The account is marked reconnect_required. | No |
rate_limited | The platform throttled the request. | Yes |
platform_error | Any other platform failure, such as an outage or unexpected response. | Usually |
Retries use exponential backoff, up to 5 attempts in total. While a target waits for a retry, its status is back to queued and errorCode / errorMessage show the last error. See Idempotency & Retries.
Read publish errors with GET /v1/posts/{id}, or receive them in post.partial and post.failed webhook notifications, where each target includes its error message.
Handling errors
| Status | What to do |
|---|---|
400 | Don't retry unchanged. Show details to the user, or fix the request. |
401 | Check the key is set and hasn't been revoked. |
402 | Add credits or reduce the number of targets. |
404 | Check the ID and that you're using the right project's key. |
429 | Wait for the next minute, then retry. See Rate Limits. |
500, network errors, timeouts | Retry with backoff, reusing the same Idempotency-Key. |
This page is also available as Markdown.