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)" }
      ]
    }
  }
}
FieldDescription
codeStable identifier. Branch on this.
messageHuman-readable explanation. It may change, so don't parse it.
detailsOptional 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

StatusMeaning
200OK. Also returned when an Idempotency-Key replays an existing post.
201Created: a post was accepted, or a Bluesky account connected.
400The request is invalid. Fix it before retrying.
401Missing or invalid access key.
402Not enough credits.
404The post or route doesn't exist (or belongs to another project).
413The request body is larger than 1 MB.
429Rate limit exceeded.
500Unexpected server error. Safe to retry with backoff.

Request error codes

CodeStatusWhendetails
invalid_json400The body isn't valid JSON.
validation_error400The 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_account400An accountId isn't in your project.{ "missing": [ids] }
account_inactive400An account needs reconnecting.{ "accountIds": [ids] }
unknown_media400A media:// reference isn't in your media library, in media or in options.{ "missing": ["media://..."] }
invalid_credentials400Bluesky rejected the identifier or app password.
unauthorized401No access key, or the key is unknown or revoked.
insufficient_credits402The project doesn't have enough credits for every target.
not_found404Unknown post ID or route.
rate_limited429More requests than the per-minute limit for this key.
internal_error500Something 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:

show-errors.ts
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.

errorCodeMeaningRetried automatically
media_errorThe 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_contentThe platform rejected the content, for example a duplicate post or a format it won't accept.No
auth_revokedThe platform no longer accepts the account's credentials. The account is marked reconnect_required.No
rate_limitedThe platform throttled the request.Yes
platform_errorAny 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

StatusWhat to do
400Don't retry unchanged. Show details to the user, or fix the request.
401Check the key is set and hasn't been revoked.
402Add credits or reduce the number of targets.
404Check the ID and that you're using the right project's key.
429Wait for the next minute, then retry. See Rate Limits.
500, network errors, timeoutsRetry with backoff, reusing the same Idempotency-Key.

This page is also available as Markdown.