Errors & retries
v1Every error has a code, a message, and a request ID. Handle the uncertainty as carefully as the happy path.
A consistent error format
{
"error": {
"code": "validation_error",
"message": "Choose at least one account."
},
"request_id": "req_example"
}Use the stable code for program logic. The message is intended for diagnosis. Store the request ID with your own logs; do not log bearer keys or connected-account credentials.
| HTTP | Meaning | Action |
|---|---|---|
| 400 | Invalid input or unsupported option | Correct the request using error details |
| 401 | Missing, invalid, or revoked key | Check the key and environment |
| 403 | Insufficient scope or account capability | Grant the required scope or reconnect the account |
| 404 | Resource not found in this workspace/mode | Check ID and key environment |
| 409 | Conflicting state, idempotency payload, or a plan’s account limit | Inspect the existing post; for account errors, disconnect an account or upgrade |
| 429 | Per-minute rate or monthly allowance reached | Check the code: wait for Retry-After, or until reset_at for an allowance |
| 5xx | Temporary service or platform failure | Retry safe operations with backoff |
Plan limit errors
These codes mean a plan limit was reached. Rejected new work incurs no charge. Previously accepted posts remain available for status checks, exact retries and cancellation; a delivery may already have reached a platform. Plans and limits lists every field.
| Code | HTTP | When | What to do |
|---|---|---|---|
usage_limit_exceeded | 429 | A monthly allowance would be exceeded | Wait until details.reset_at or upgrade. Never retry sooner. |
rate_limit_exceeded | 429 | The per-minute request rate was reached | Wait for Retry-After, then retry |
account_limit_reached | 409 | Connecting one more account would exceed the plan | Disconnect an account or upgrade |
account_capacity_exceeded | 409 | More accounts are connected than the plan allows, for example after a downgrade | Disconnect accounts or upgrade. Nothing is deleted. |
Idempotency is part of the request
Choose a unique idempotency key for each logical post. Use that same key and the same payload after a timeout. Reusing a key with a different payload is a conflict; use a new key only for a genuinely new post.
try {
await castrook.posts.create(payload, {
idempotencyKey: 'campaign-42-post-7',
});
} catch (error) {
if (error instanceof CastrookError) {
console.error(error.code, error.requestId);
}
}An uncertain result needs inspection
A platform can accept a write before a network timeout. Castrook does not blindly repeat an ambiguous publish. Inspect the target error and the platform account before creating a replacement. The same caution applies to comment replies, which are not idempotent.
Retry safe operations
The TypeScript client retries GET requests and posts with idempotency keys on transport failures, rate_limit_exceeded, and 5xx. It waits for a Retry-After of up to 30 seconds and uses a request timeout. It never retries usage_limit_exceeded or account limit errors, and it does not automatically repeat comment replies.
Do not treat HTTP 202, an upload ID, or a platform processing receipt as proof of public publication. Check the final destination state and visibility.