Every error arrives in the same envelope. The code is stable and is what your code should branch on; the message is for humans, written in Portuguese, and may change.
{ "statusCode": 400, "code": "MEDIA_INVALID", "message": "imagem acima de 8 MB", "details": { "reason": "too_large" } }
details is optional and carries the context that helps you act: the accountId that needs reconnecting, the reason of a rejected image, the account's quota.
Authentication and access
| Code | HTTP | Cause | What to do |
|---|
UNAUTHORIZED | 401 | Key missing, invalid, revoked or expired; Bearer and key together; invalid Bearer. | Check the header. Create another key if the current one was revoked. |
FORBIDDEN | 403 | The project or organization does not belong to the token's user. | Dashboard mode only. Check x-project-id. |
PROJECT_REQUIRED | 400 | Bearer mode without x-project-id on a resource route. | Dashboard mode only. |
RATE_LIMIT_KEY | 429 | Request limit of the credential exceeded. | Wait for x-ratelimit-reset. See Limits. |
PERMISSION_REQUIRED | 403 | The account was connected without the permission the route requires (details.scope). | Reconnect the account. |
Request
| Code | HTTP | Cause | What to do |
|---|
VALIDATION_ERROR | 400 | Missing field, wrong type, out of range, scheduledFor outside the window, unknown mediaId. | The message names the field (scheduledFor: …). Fix and resend. |
NOT_FOUND | 404 | Post, account, profile or key that does not exist in this project. | Check the id. A resource from another project also answers 404. |
Account connection
| Code | HTTP | Cause | What to do |
|---|
PLATFORM_NOT_AVAILABLE | 403 | Platform other than instagram. | See the Roadmap. |
STATE_INVALID | 400 | The OAuth return state is missing, tampered with or older than 10 minutes. | Start the connection again. |
AUTHORIZATION_DENIED | 400 | The user denied authorization on Instagram. | Explain why it is needed and offer to retry. |
ACCOUNT_NOT_BUSINESS | 400 | Personal account. | The user switches to a professional account in Instagram's settings and connects again. |
TOKEN_INVALID | 409 | Token expired or revoked (password changed, app removed). On POST /v1/posts, the target account needs reconnecting. | Send the user to connect the account again; the existing connection is updated. |
ACCOUNT_RESTRICTED | 409 | Meta restricted the account. | Only the account owner can resolve it, on Instagram. |
Publishing
| Code | HTTP | Cause | What to do |
|---|
RATE_LIMIT_ACCOUNT | 409 | The account exhausted Meta's publishing quota (100 per 24 h). details carries quotaUsage and quotaTotal. | Schedule for later or use another account. |
RATE_LIMIT_APP | 503 | Global app limit on Meta. Temporary. | Postmatic retries accepted posts on its own; for new ones, wait a few minutes. |
SPAM_SUSPECTED | 409 | Meta flagged the content as spam. | Change the caption or the images. |
MEDIA_INVALID | 400 | Image or video outside the limits: missing, too_many, too_large, mime, width, aspect, duration, fps, codec, audio, bitrate or moov in details.reason. | See Media. |
MEDIA_FETCH_FAILED | 400 | The URL answered an error or is not https. | Check the URL. |
MEDIA_UNREACHABLE | 400 | The URL could not be reached (DNS, timeout, private address). | Make sure it is public. |
CONTAINER_EXPIRED | 409 | Meta took too long to process the media. | Retries are automatic. |
CONTAINER_STUCK | 409 | The container stayed IN_PROGRESS for over 1 hour without finishing. | Terminal — create a new post; no automatic retry. |
MESSAGE_WINDOW_CLOSED | 409 | Direct message outside the 24-hour window since the recipient's last message (details.recipientId, details.lastInboundAt). | Only a new message from the recipient reopens the window; waiting alone does not. |
POST_NOT_CANCELLABLE | 409 | The post already started publishing, already finished, or its time has passed. | Nothing to do; read the current state. |
Infrastructure and callbacks
| Code | HTTP | Cause | What to do |
|---|
PLATFORM_CREDENTIALS_MISSING | 500 | Meta app configuration missing on the server. | Our fault. Contact support. |
SIGNED_REQUEST_INVALID | 400 | The signature of a Meta callback does not match. | Only on the routes Meta calls. |
INTERNAL_ERROR | 500 | Unexpected error. | Retry later; if it persists, contact support with the timestamp. |
Per-target errors
Once accepted, a post no longer returns HTTP errors: each target in platforms[] records its outcome in errorCode and errorMessage. The codes are the same as in the tables above, plus two that only exist in that context:
errorCode | Meaning |
|---|
CANCELLED | You cancelled the post before it published. |
PUBLISH_UNCONFIRMED | Meta accepted the publish but did not confirm the result in time. The post may exist on Instagram; check before resending. |
When to retry
- Safe to retry:
RATE_LIMIT_KEY (after the reset), RATE_LIMIT_APP, INTERNAL_ERROR. Use the same idempotencyKey so you never duplicate a post.
- Do not retry without changing something:
VALIDATION_ERROR, MEDIA_*, SPAM_SUSPECTED, ACCOUNT_NOT_BUSINESS.
- Needs the user:
TOKEN_INVALID, ACCOUNT_RESTRICTED, AUTHORIZATION_DENIED.