Skip to main content

Errors

Mixpost returns conventional HTTP status codes, and every error body is JSON with at least a message key.

Error format

All errors share the same envelope:

{
"message": "Unauthenticated."
}

Validation errors (422) add an errors object keyed by field name:

{
"message": "The name field is required. (and 1 more error)",
"errors": {
"name": ["The name field is required."],
"hex_color": ["The hex code is not valid"]
}
}

The top-level message on a 422 is Laravel's summary of the first error, not a fixed string. Match on the errors keys rather than on message.

Status codes

CodeMeaning
200Success.
201Created. See which endpoints return 201.
401The bearer token is missing, malformed, unknown or expired.
403The token is valid, but the user may not perform this action. See Permissions.
404The workspace or the record does not exist.
422The request body failed validation.
500Unhandled server error.

Created responses

Most successful calls return 200. These five return 201:

  • POST /{workspace}/tags
  • POST /panel/workspaces
  • POST /panel/workspaces/{workspace}/subscription
  • POST /panel/users
  • POST /panel/receipts

If you check for an exact 200, these will look like failures. Treat any 2xx as success.

401 Unauthenticated

{
"message": "Unauthenticated."
}

Returned when Authorization: Bearer <token> is absent, the token does not match a stored token, or the token's expiry has passed. Tokens are matched by hash, so a token that was revoked in the dashboard fails the same way as one that never existed.

403 Access forbidden

{
"message": "Access forbidden."
}

The token authenticated successfully, but the user behind it is not permitted. This is returned when the user is not a member of the workspace in the URL, when their workspace role is too low for the route, or — on /panel endpoints — when the user is not a platform admin.

On Mixpost Enterprise, a workspace whose plan has the API feature turned off is also refused with a 403, with a message describing the disabled feature rather than the text above. This does not apply to Mixpost Pro, where the API is not gated per plan.

404 Not found

An unknown workspace UUID returns:

{
"message": "Workspace not found."
}

A workspace segment that is not a UUID at all matches no route, so it returns 404 body instead, before authentication runs.

An unknown record on a workspace endpoint returns the model name:

{
"message": "Post not found."
}

The message follows the model — Post not found., Tag not found., Media not found., Account not found.

Enterprise panel endpoints differ

The /panel/* endpoints (Workspaces, Subscriptions, Users, Receipts) do not produce these friendly messages. Mixpost Enterprise registers no exception handler for them, so a missing record returns Laravel's default 404 body instead. Rely on the status code, not the message.

422 Validation failed

{
"message": "The name field is required.",
"errors": {
"name": ["The name field is required."]
}
}

Media endpoints have one extra 422 case worth handling: when a workspace has hit a plan limit, the body carries an additional top-level limit object alongside errors.limit.

Publishing requirements

Before a post is committed to publishing, its content is checked against what the platform of each of its accounts needs. The check runs when a post is scheduled, added to the queue, approved, or created with schedule, schedule_now or queue. It runs again when a scheduled post is updated and stays scheduled, so the post cannot be changed into one its platforms would refuse. A draft is never checked, so unfinished content can be saved and completed later.

A scheduled post can still stop meeting a requirement without being updated through the API: it can be edited in the dashboard, which saves every change as it is made, or lose a media file or a Pinterest board it relies on. Each account is checked once more right before it is published. An account that fails the check is not sent to its platform — a thread is never published up to the post at fault — and it is marked failed, with the requirement's message in its errors, exactly as a platform's own rejection would be.

Every unmet requirement is its own entry in errors:

{
"message": "Instagram → Add a photo or a video. (and 1 more error)",
"errors": {
"requirements.0.instagram.post_0": ["Instagram → Add a photo or a video."],
"requirements.12.pinterest.board": ["Pinterest (Home Decor) → Choose a board."]
}
}

A key reads requirements.<scope>.<provider>.<rule>:

  • <scope> is 0 when the problem is in the original content, shared by every account without a version of its own. It is an account ID when the problem is in that account's own version, or in an option kept per account (a Pinterest board, a TikTok privacy level).
  • <provider> is the platform, such as instagram or pinterest.
  • <rule> names the requirement:
RulePlatformsRequirement
post_<n>All but Google Business ProfilePost n (from 0) has what the platform publishes: text or media on X, Threads, Bluesky, Mastodon, LinkedIn and Facebook; a photo or video on Instagram, Pinterest and Pixelfed; a video on YouTube and TikTok (photos too, when TikTok photo uploads are enabled); exactly one video for a reel; exactly one photo or video for a story. Threads are checked post by post, other platforms on their first post only.
text_<n>AllThe text of post n fits the platform's limit, counted the way the platform counts it: on X links count 23 characters and emoji or wide characters 2; on Mastodon and Pixelfed links count 23 and a mention leaves out its server; on Bluesky links count as the shortened form Bluesky shows. On platforms with comments the first comment is held to the limit too.
photos_<n>, videos_<n>, gifs_<n>AllPost n carries no more photos, videos or GIFs than the platform accepts.
hashtags_<n>Instagram, FacebookPost n has no more hashtags than the platform allows: 5 on Instagram, 30 on Facebook. Stories are not checked.
media_items_<n>PixelfedPost n carries no more media in total than the instance accepts.
mixed_media_<n>X, Bluesky, Mastodon, Facebook, LinkedIn, Pinterest, YouTube, TikTok, Google Business ProfilePost n does not mix photos, videos and GIFs.
first_commentFacebook, Instagram, LinkedInA first comment that is present has text. Stories publish no comment and skip this check.
boardPinterestoptions.pinterest.boards.account-<id> holds a board for the account.
linkPinterestoptions.pinterest.link, when set, is an http or https URL.
titleYouTube, Google Business ProfileYouTube: options.youtube.title is set and at most 100 characters. Google Business Profile offers and events: options.gbp.event_title is set.
datesGoogle Business ProfileOffers and events have a start_date and an end_date, and the end is not before the start.
button_linkGoogle Business ProfileA button other than NONE or CALL has a valid button_link.
offer_linkGoogle Business ProfileAn offer with offer_has_details has a valid offer_link, when one is set.
privacy_levelTikTok (direct posting)options.tiktok.privacy_level.account-<id> is set.
content_disclosureTikTok (direct posting)When content_disclosure is on, brand_organic_toggle or brand_content_toggle is on as well.

The messages are translated, so match on the keys rather than on the text.

500 Server error

{
"message": "Server Error"
}

When your instance runs with APP_DEBUG=true, this body also includes exception, file, line and trace. Never run a production instance with debug enabled.

Rate limiting

Mixpost does not rate limit the API. No throttle is applied to any API route by the package itself.

However, Mixpost's API routes run inside your host Laravel application's api middleware group, and that group is yours to configure. Laravel 8–10 skeletons include throttle:api (60 requests per minute) in that group by default; Laravel 11 and newer do not, unless the application opts in via ->throttleApi().

So whether you ever receive a 429 — and at what threshold — depends entirely on the application hosting Mixpost. If throttling is enabled there, responses carry the standard Retry-After and X-RateLimit-* headers. Treat 429 as possible and back off on it, but don't assume a fixed budget.

Permissions

A token carries the full authority of the user who created it — there are no per-token scopes. What an endpoint allows is decided by that user's role in the workspace.

ADMIN and MEMBER have identical access across the API; no endpoint distinguishes them. The only meaningful distinction is VIEWER:

EndpointsADMINMEMBERVIEWER
GET accountsyesyesyes
GET posts, POST /posts/approve/{post}yesyesyes
All other post endpointsyesyes
GET /media, GET /media/{media}yesyesyes
All other media endpointsyesyes
All tag endpoints, including readsyesyes

Two results here are easy to trip over:

  • A VIEWER can approve a post but cannot create one.
  • A VIEWER cannot read tags. Unlike posts and media, whose read endpoints are open to viewers, every tag endpoint requires ADMIN or MEMBER — so GET /tags returns 403 for a viewer.

The /panel/* endpoints are not in the table because they ignore workspace roles entirely: they require a platform administrator account, whatever the user's role in any workspace.

Approving a post has two further conditions beyond role: the user must have approval rights in the workspace, and the workspace's plan must allow scheduling.