Getting started
Read the error envelope, branch on type and code, know which failures to retry, and handle per-platform publishing errors.
Pozy uses HTTP status codes and returns a JSON body on every failed request. Branch your code on the HTTP status and the code field. Show error to people, but never parse it: the wording can change.
#The error envelope
{
"error": "`name` must be 1 to 50 characters.",
"type": "invalid_request_error",
"code": "invalid_field",
"param": "name"
}| Field | Always present | Meaning |
|---|---|---|
error |
Yes | A sentence for humans. May be reworded at any time. |
type |
Usually | The class of error. See the table below. |
code |
Usually | A stable, machine-readable reason, such as read_only_key. |
param |
When one field is at fault | The field or query parameter, dotted when nested. |
details |
Sometimes | Structured context: the conflicting id, field errors, limits. |
platform, platformError |
When a platform refused | Which platform said no, and its original error payload. |
Some endpoints add their own fields next to these. Payment errors carry reason and dashboard_url, for example (billing).
#Status codes and types
| Status | type |
When |
|---|---|---|
| 400 | invalid_request_error |
A field is missing or invalid, the JSON is malformed, or a precondition is not met. |
| 401 | authentication_error |
No credential, or it is invalid, revoked or expired. |
| 402 | payment_required |
The action needs a payment method or would pass a spend cap, or the workspace is paused because a payment failed. |
| 403 | permission_error |
The credential is valid but not allowed: read-only key, disabled group, role, scope, an unconfirmed email address, a feature that is not enabled, or a workspace paused by Pozy. |
| 404 | not_found |
The resource does not exist, or this credential cannot see it. |
| 405 | invalid_request_error |
The path exists but not with this method. error lists the allowed methods. |
| 409 | invalid_request_error |
A conflict: a duplicate post, an externalId already in use, a queue slot already taken, or a request with the same Idempotency-Key still running. |
| 422 | invalid_request_error |
The request is well formed but cannot be processed as sent. |
| 429 | rate_limit_error |
Too many requests, or a daily cap was reached. Wait Retry-After seconds when present (rate limits). |
| 500 | api_error |
Something failed on Pozy's side. |
| 501 | api_error |
The endpoint exists in the spec but is not available yet (not_implemented). |
| 502 | platform_error or api_error |
A platform returned an error or did not answer, or Pozy could not send an email. |
| 503 | platform_error |
A platform service Pozy relies on is unavailable right now. Nothing is wrong with your request. Retry later, after Retry-After seconds when present. |
#Common codes
code |
Status | Meaning |
|---|---|---|
invalid_api_key |
401 | Missing or invalid credential. |
read_only_key |
403 | A write with a read-only key. |
resource_group_disabled |
403 | The key cannot use this area of the API. |
insufficient_permissions |
403 | The key type cannot do this. |
role_forbidden |
403 | Your workspace role cannot do this. |
profile_out_of_scope |
403 | A profile-scoped key named another profile, or reached for the balance, transfers or payouts. |
account_out_of_scope |
403 | A profile-scoped key named an account that belongs to another profile. |
feature_not_available |
403 | Not enabled for the workspace. Contact support@pozy.ai. |
payment_required |
402 | Add a payment method to continue. |
workspace_suspended |
402 | The workspace is paused because a payment failed. Update the card in Billing to resume. Reads, billing and paying keep working; writes, connects and publishing are refused. |
workspace_suspended |
403 | Pozy paused the workspace. Write to support@pozy.ai to resume it. |
email_not_verified |
403 | Confirm your email address first, or add a payment method. Needed to send invitation emails and to connect accounts without a payment method. |
missing_field, missing_required_field |
400 | A required field is absent. param names it. |
invalid_field, invalid_field_value |
400 | Wrong type, format or value. param names it. |
method_not_allowed |
405 | Wrong HTTP method for this path. |
endpoint_not_found |
404 | No endpoint at this method and path. Check for typos and the /v1 prefix. |
resource_not_found |
404 | The id is unknown or not visible to this credential. Specific variants exist, such as profile_not_found and webhook_not_found. |
rate_limit_exceeded |
429 | Your workspace used its requests for this minute. |
rate_limited |
429 | A platform or a per-endpoint quota said wait. |
invite_limit_reached |
429 | The workspace sent its invitation emails for today (20 without a payment method, 500 with one), or has 50 email invitations waiting without a payment method. Share an invite link instead. |
invite_resend_too_soon |
429 | An invitation can be emailed again once an hour. |
support_ticket_limit |
429 | A workspace without a payment method sent its 5 questions to the Pozy team for today. Write to support@pozy.ai for anything urgent. |
idempotency_key_in_use |
409 | A request with this Idempotency-Key is still running. Retry in a moment with the same key. |
email_not_sent |
502 | Pozy could not send the email. Copy the invite link and share it yourself. |
service_unavailable |
503 | Pozy cannot reach a platform service it relies on. Retry later. |
temporarily_unavailable |
503 | A platform service Pozy relies on is temporarily unavailable, for example for new connections. Retry later. |
platform_api_error |
4xx or 502 | The platform refused the call. Read platform and platformError. |
not_implemented |
501 | Not available yet. |
New codes can appear at any time, so always keep a default branch.
#Platform errors
When Instagram, Google, Meta or another platform refuses a request, the response says which one and includes its payload untouched:
{
"error": "Google rejected the ad: NOT_ENOUGH_SQUARE_MARKETING_IMAGE_ASSET",
"type": "platform_error",
"code": "platform_api_error",
"platform": "google",
"platformError": { "code": 3, "message": "Request contains an invalid argument." }
}A platform 4xx usually means the input needs fixing. A 502 means the platform failed or timed out, and a retry often works.
#Publishing errors live on the post
A scheduled post fails later, long after your request returned 200. Those failures do not come back as an error response. Instead, the post ends with status: "failed" (no target published) or "partial" (some did), and each failed entry in platforms[] explains why:
{
"platform": "instagram",
"status": "failed",
"errorMessage": "The aspect ratio of this video is not supported for reels.",
"errorCategory": "user_content",
"errorSource": "user"
}errorCategory |
Meaning | What to do |
|---|---|---|
auth_expired |
The platform revoked access. | Ask the owner to reconnect the account, then retry. |
user_content |
The content broke a platform rule: length, format, media size. | Fix the post and retry. |
user_abuse |
The platform flagged the content or the account. | Review the content. Do not retry as is. |
account_issue |
The account is restricted or missing a permission. | Fix it on the platform side. |
platform_rejected |
The platform refused without a clearer reason. | Read errorMessage, adjust, retry. |
platform_error |
The platform failed on its side. | Retry later. |
platform_rate_limit |
The platform throttled the account. | Retry later. |
quota_exhausted |
A daily or per-app quota is used up. | Retry after the quota resets. |
system_error |
Pozy failed to publish. | Retry. Contact support if it repeats. |
unknown |
Not classified. | Read errorMessage. |
errorSource tells you who has to act: user, platform or system. POST /v1/posts/{postId}/retry runs only the failed targets again. Subscribe to post.failed, post.partial and post.platform.failed to hear about these as they happen (post lifecycle).
Publishing now (publishNow: true) or retrying can return 207 Multi-Status when the post was saved but at least one platform failed. Most HTTP clients treat 207 as success, so check post.status as well as the status code.
#What to retry
| Response | Retry? | How |
|---|---|---|
429 |
Yes | Wait Retry-After seconds, then send the same request. |
500, 502, 503 |
Yes | Back off exponentially (1 s, 2 s, 4 s, up to about a minute). For creates, send the same Idempotency-Key (idempotency). |
| Network timeout | Yes, carefully | The request may have succeeded. Retry with the same Idempotency-Key, or read the resource first. |
409 idempotency_key_in_use |
Yes | Wait a moment, then send the same request with the same Idempotency-Key. |
Other 409 |
No | Read details. A duplicate post comes with the id of the existing one. |
Other 4xx |
No | Fix the request. Retrying the same body fails the same way. |
#Request ids
Every response carries an X-Request-Id header. Log it with the error. Support can trace any request from that id (support).
You can send your own x-request-id (for example the id of your job) and Pozy uses it for that request, so your logs and Pozy's line up.
curl -i https://api.pozy.ai/v1/posts/6721a0c47b2e5f9d3a18c6e0 \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "x-request-id: 3e0f7a52-9b1c-4c6d-8f2e-5a7b9c1d3e4f"#Handle errors in code
// A network error throws here: it is safe to retry with the same Idempotency-Key.
const res = await fetch("https://api.pozy.ai/v1/posts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ content: "Hello", platforms: [] }),
});
if (!res.ok) {
const body = await res.json();
const requestId = res.headers.get("x-request-id");
if (res.status === 429 || res.status >= 500) {
// retry later, see "What to retry"
} else if (body.type === "invalid_request_error") {
console.error(`Fix ${body.param ?? "the request"}:`, body.error, body.details);
} else {
console.error(res.status, body.code, body.error, `request ${requestId}`);
}
}import os, requests
resp = requests.post(
"https://api.pozy.ai/v1/posts",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
json={"content": "Hello", "platforms": []},
timeout=30,
)
if not resp.ok:
body = resp.json()
request_id = resp.headers.get("x-request-id")
if resp.status_code == 429 or resp.status_code >= 500:
pass # retry later, see "What to retry"
else:
raise RuntimeError(f"{resp.status_code} {body.get('code')}: {body.get('error')} (request {request_id})")