yeetpostyeetpost

Errors

Every code the API can return, generated from the spec, with what it means and what to do about it.


#The shape

Errors are JSON, always with a code and a message:

error
{
  "error": "invalid_connection",
  "message": "Connection 'linkedn' not found. Available connections: linkedin, x"
}

platform_rejected and internal_server_error also carry a req_id. The 429 body is the exception: it is { "error": "too many requests" } and has no message.

Inside a post, on a results item or a posts item, the same vocabulary shows up as { "code": ..., "message": ... }. A fan-out to several connections reports a per-connection failure that way, inside a 200, rather than failing the request.

#Every code

missed_schedule

The post's exact scheduled time passed without it going out. It never sent.

what to doReschedule it.

missed_slot

The post's slot passed without it going out. It never sent.

what to doReschedule it.

send_interrupted

yeetpost stopped mid-send on a scheduled or queued post; whether it actually went out is unknown. This never comes back from a request you made, only stored on a post's own error.

what to doCheck the platform before doing anything else. Never resend blindly: it may have already posted.

queue_empty200

You sent queue: true and that connection has no posting slots at all, so there is nothing to queue into.

what to doAdd slots with PATCH /connections/{connectionId}/queue, or name a scheduledFor instead. On a draft, the draft stays where it was, so publish the same id again once there are slots.

  • That connection has no posting slots. Add some with PATCH /connections/{connectionId}/queue.

POST /posts

queue_full200

The connection has slots, but every one of them inside the next 30 days is already taken.

what to doCancel a queued post, add more slots, or name a scheduledFor. GET /connections/{connectionId}/queue shows what is holding the slots.

  • Every posting slot within the next 30 days is taken. Cancel a queued post or add more slots.

POST /posts

reconnect_required200

The connection's login is missing, expired or revoked, or a Discord webhook was deleted, or the stored authorisation no longer covers what the post needs (posting images to X after the image permission was added is the case that fires most).

what to doReconnect that account: in the dashboard for your own accounts, or with POST /profiles/{profileId}/connect for a customer's. Nothing about the request itself will fix it.

  • Reconnect your X account to post images (a new permission is needed).

POST /posts

delete_unsupported400

The post went out to a platform that has no delete. Instagram and Threads cannot take a post back either, and SMS, email and Slack cannot take a message back once it is sent.

what to doNothing on our side. The post stays up; remove it in the platform's own app if it has a way to. X, Bluesky, Mastodon, Telegram, Discord, LinkedIn, Reddit and Facebook do have a delete.

  • A slack message cannot be taken back once it is sent.

DELETE /posts/{postId}

invalid_connection400

None of the slugs you sent match a connection of yours. On the fan-out endpoint this only fires when none of them match: one bad slug among good ones is a failed item inside a 200.

what to doCall GET /connections and post to a slug from that list. The message names the ones you do have.

  • Connection 'linkedn' not found. Available connections: linkedin, x
  • None of the given connections were found: linkedn, twiter

POST /post/{connectionSlug} · POST /posts

invalid_media400

A media id you attached is not a valid id, or is not one of yours. Media somebody else owns and media that never existed give the same answer, so an id cannot be probed for existence.

what to doUpload the image with POST /media and attach the id it returns. The message names the ids that did not resolve.

  • Media not found: 9b1c2d3e-4f50-4a6b-8c7d-1e2f3a4b5c6d

PATCH /posts/{postId}

invalid_media_type400

The bytes you uploaded are not the image type the Content-Type header claims. The header only says what you claim the bytes are, and anything stored is served back under that claim later.

what to doSend the real type of the file: image/png, image/jpeg, image/gif or image/webp.

  • The bytes do not match the content type image/png.

POST /media

invalid_request400

The request itself is wrong: a malformed body, bad query parameters, a scheduled time in the past or more than 30 days out, both scheduledFor and queue at once, an edit of something that is not a draft, or a post that cannot be cancelled any more.

what to doRead the message, fix the request, then retry. Retrying the same request unchanged gives the same answer.

  • profileId must be the id of one of your profiles.
  • connectionId must be a valid connection id.
  • Request body must be JSON with profileId, either a profile id or null.
  • slots must be at most 50 unique entries of { weekday: 0-6, time: "HH:MM" }.
  • Unknown timezone 'Europe/Helsinky'. Use a zone name such as Europe/Helsinki, not a UTC offset.
  • Unknown field: queueSlots. Use slots.
  • Request body must be JSON with a name of 1 to 100 characters and an optional externalId of 1 to 200 characters.
  • Invalid query parameters. limit must be between 1 and 100. offset must be 0 or greater.
  • profileId must be a valid profile id.
  • redirectUrl must be an https URL on a public host, with no fragment. Plain http is accepted for localhost only.
  • Request body must be JSON with a name of 1 to 80 characters, a type of live or test, and an optional redirectAllowlist of at most 20 origins.
  • Invalid query parameters. includeRevoked must be true or false. limit must be between 1 and 100. offset must be 0 or greater.
  • profileId and keyId must be valid ids.
  • keyId must be a valid API key id.
  • connectSessionId must be a valid connect session id.
  • Request body must not be empty.
  • mediaId must be a valid media id.
  • Scheduled time must be in the future.
  • Request body must be JSON with a non-empty text and a non-empty connectionSlugs array. thread takes at most 24 non-empty texts.
  • Invalid query parameters. status must be one of sent, scheduled, processing, draft, failed, cancelled, deleted. profileId must be a valid profile id. limit must be between 1 and 100. offset must be 0 or greater.
  • postId must be a valid post id.
  • Only drafts can be edited.
  • Send either scheduledFor or queue, not both.
  • url must be an https URL.
  • webhookId must be a valid webhook id.
  • Invalid query parameters. limit must be between 1 and 100, status must be one of pending, delivered, failed, dead, and startingAfter must be a delivery id.
  • This delivery is already queued for another attempt.
  • Invalid query parameters. days must be between 1 and 90.

GET /connections · GET /connections/{connectionId} · PATCH /connections/{connectionId} · GET /connections/{connectionId}/queue · PATCH /connections/{connectionId}/queue · POST /profiles · GET /profiles · GET /profiles/{profileId} · PATCH /profiles/{profileId} · DELETE /profiles/{profileId} · POST /profiles/{profileId}/connect · POST /profiles/{profileId}/keys · GET /profiles/{profileId}/keys · DELETE /profiles/{profileId}/keys/{keyId} · GET /keys · DELETE /keys/{keyId} · GET /connect-sessions/{connectSessionId} · POST /media · GET /media/{mediaId} · DELETE /media/{mediaId} · POST /post/{connectionSlug} · POST /posts · GET /posts · GET /posts/{postId} · PATCH /posts/{postId} · POST /posts/{postId}/publish · GET /posts/{postId}/metrics · POST /webhooks · DELETE /webhooks/{webhookId} · GET /webhooks/{webhookId}/deliveries · POST /webhooks/{webhookId}/deliveries/{deliveryId}/replay · POST /webhooks/{webhookId}/test · GET /health/history

media_unsupported400

That connection's platform cannot carry images. On a fan-out this is a per-connection failure inside a 200, not a failed request.

what to doPost the text without images to that connection, or drop it from connectionSlugs.

  • sms posts cannot carry images.

PATCH /posts/{postId}

shape_unsupported400

The connection cannot carry the shape you sent: a thread anywhere but X, Bluesky, Telegram and Mastodon, or a firstComment anywhere but LinkedIn and Facebook.

what to doSend the shape only to connections that take it. Everything else can have the plain text.

  • linkedin posts cannot carry a thread.

PATCH /posts/{postId}

unauthorized401

The API key is missing, malformed, or no longer valid. Inside a post item (on a results or posts entry), unauthorized means something else entirely: the platform itself refused the connection's stored token, the same cause as reconnect_required.

what to doSend the key as x-api-key or Authorization: Bearer. If it was deleted, create a new one in settings. Inside a post item, reconnect the account instead.

  • Unauthorized (missing API key; set the x-api-key header or Authorization: Bearer <key>)

GET /connections · GET /connections/{connectionId} · PATCH /connections/{connectionId} · GET /connections/{connectionId}/queue · PATCH /connections/{connectionId}/queue · POST /profiles · GET /profiles · GET /profiles/{profileId} · PATCH /profiles/{profileId} · DELETE /profiles/{profileId} · POST /profiles/{profileId}/connect · POST /profiles/{profileId}/keys · GET /profiles/{profileId}/keys · DELETE /profiles/{profileId}/keys/{keyId} · GET /keys · DELETE /keys/{keyId} · GET /connect-sessions/{connectSessionId} · POST /media · GET /media/{mediaId} · DELETE /media/{mediaId} · POST /post/{connectionSlug} · POST /posts · GET /posts · GET /posts/{postId} · DELETE /posts/{postId} · PATCH /posts/{postId} · POST /posts/{postId}/publish · GET /posts/{postId}/metrics · GET /analytics · POST /webhooks · GET /webhooks · DELETE /webhooks/{webhookId} · GET /webhooks/{webhookId}/deliveries · POST /webhooks/{webhookId}/deliveries/{deliveryId}/replay · POST /webhooks/{webhookId}/test

connection_limit_exceeded402200

The free plan caps posting at 2 active connections at once (a connection with a successful send in the last 30 days); this one is idle, or it has aged out of your 2 most recently active. Connecting is always free and never blocked, this only affects posting.

what to doSubscribe, wait 30 days for an older active connection to go idle, or post to one of your 2 most recently active connections instead.

  • This account already posted to 2 accounts in the last 30 days, the free limit. Subscribe to post to more, or wait until one has been idle for 30 days.

POST /post/{connectionSlug} · POST /posts

payment_method_required402

Posting to X bills a flat fee per post and your account has no payment method. LinkedIn is unaffected.

what to doAdd a payment method in Settings. Subscribing at two accounts is still $0 a month, so adding a card does not push you onto a paid tier.

  • X charges per post. Add a card and pay a flat 2 cents per post, 25 cents with a link, nothing else.

POST /post/{connectionSlug}

spend_cap_reached402

Your own monthly X spend cap is reached.

what to doRaise the cap in Settings, or wait for the month to roll over. Posts to other platforms keep going out.

  • Your monthly X spend cap is reached. Raise it in Settings.

POST /post/{connectionSlug}

fair_use_reached403

The free plan's daily fair-use cap is used up for today. Paid accounts have no daily cap.

what to doWait for the day to turn over, or add a payment method. A draft that hits this stays a draft, so the same id publishes tomorrow.

  • Fair-use cap reached for today. Paid accounts have no daily cap.

POST /post/{connectionSlug}

forbidden403

The key does not have the capability, whatever the id. Two cases. The key is scoped to one profile and the call is outside that scope: managing profiles is that, and so is key management, so creating, listing, renaming and deleting profiles and all five key endpoints need an unscoped key, while a scoped key may only read its own profile and open a connect session for it. Or a test key asked to mint a live key.

what to doUse an unscoped key to manage profiles and keys, and keep the scoped key for that customer's connections, posts, media and webhooks. Mint live keys with a live key. Objects outside the scope answer 404 rather than 403, so a 403 always means the capability, never the id.

  • This API key is scoped to profile a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d, which cannot move connections between profiles. Use an unscoped key for that.
  • This connection is used to sign in to yeetpost, so it cannot be moved into a profile.
  • This API key is scoped to profile b2d1f0a3-5c6e-4f70-8a9b-0c1d2e3f4a5b, which cannot manage profiles. Use an unscoped key for that.
  • This API key is scoped to profile b2d1f0a3-5c6e-4f70-8a9b-0c1d2e3f4a5b, which cannot manage API keys. Use an unscoped key for that.

PATCH /connections/{connectionId} · GET /profiles · GET /profiles/{profileId} · PATCH /profiles/{profileId} · DELETE /profiles/{profileId} · POST /profiles/{profileId}/connect · GET /profiles/{profileId}/keys · DELETE /profiles/{profileId}/keys/{keyId} · GET /keys · DELETE /keys/{keyId}

limit_exceeded403

A cap is used up: messages on any plan, posts on a legacy plan, or the 50 live and 50 test keys a profile may hold at once. Posts on the plans sold today are unlimited, so this never fires for them.

what to doUpgrade the plan or wait for the month to reset. For keys, revoke one first: a revoked key does not count toward the cap, so weekly rotation never meets it. The message says which limit you hit.

  • You can have at most 1000 profiles. Delete one you no longer need before creating another.
  • You can have at most 50 live API keys in one profile. Revoke one you no longer need before creating another.
  • You have reached your monthly post limit (3). Upgrade your plan for more posts.

POST /profiles · POST /profiles/{profileId}/keys · POST /post/{connectionSlug}

not_found404

No post, webhook, connection or media of yours with that id. Something belonging to someone else is a 404 too, same as something that never existed, and so is anything outside a scoped key's profile. Publishing a draft twice gives this on the second call.

what to doCheck the id. Post ids come from POST /posts or GET /posts, webhook ids from POST /webhooks or GET /webhooks, media ids from POST /media.

  • Connection not found.
  • Profile not found.
  • API key not found.
  • Connect session not found.
  • Media not found.
  • Post not found.
  • Draft not found.
  • Webhook not found.
  • Delivery not found.

GET /connections/{connectionId} · PATCH /connections/{connectionId} · GET /connections/{connectionId}/queue · PATCH /connections/{connectionId}/queue · GET /profiles/{profileId} · PATCH /profiles/{profileId} · DELETE /profiles/{profileId} · POST /profiles/{profileId}/connect · POST /profiles/{profileId}/keys · GET /profiles/{profileId}/keys · DELETE /profiles/{profileId}/keys/{keyId} · DELETE /keys/{keyId} · GET /connect-sessions/{connectSessionId} · GET /media/{mediaId} · DELETE /media/{mediaId} · GET /media/{mediaId}/raw · GET /posts/{postId} · DELETE /posts/{postId} · PATCH /posts/{postId} · POST /posts/{postId}/publish · GET /posts/{postId}/metrics · DELETE /webhooks/{webhookId} · GET /webhooks/{webhookId}/deliveries · POST /webhooks/{webhookId}/deliveries/{deliveryId}/replay · POST /webhooks/{webhookId}/test

connection_conflict409

That profile already holds a connection to this same account on this platform.

what to doUse the existing connection instead of creating another one. Look it up with GET /connections.

  • That profile already holds a connection to this account ('acme_linkedin'). Disconnect one of them first.
  • Your own connections already include this account ('linkedin'). Disconnect one of them first.

PATCH /connections/{connectionId}

idempotency_key_conflict409

This Idempotency-Key was already used with a different body, or its first request is still running.

what to doUse a fresh key for a different post. If the first request is still in flight, wait and retry the same key: you will get the stored response back.

  • This Idempotency-Key was already used with a different request body.
  • A request with this Idempotency-Key is already in progress.

POST /posts · POST /posts/{postId}/publish

post_not_cancellable409

The worker claimed the post while your cancel was in flight. The post was not cancelled and is on its way out.

what to doRead it back with GET /posts/{postId} to see how it ended. There is nothing to retry.

  • The post was picked up for sending and can no longer be cancelled.

DELETE /posts/{postId}

profile_external_id_conflict409

Another profile of yours already carries this externalId. The id you use for a customer in your own system maps to exactly one profile.

what to doLook the customer up with GET /profiles and reuse the profile you already have, or send a different externalId. Creating a profile with no externalId at all is always allowed.

  • A profile with this externalId already exists.

POST /profiles · PATCH /profiles/{profileId}

media_storage_full413

Your stored images add up to the 50 MB allowance, so there is no room for another one.

what to doDelete images you no longer attach to anything with DELETE /media/{mediaId}, then upload again.

  • Image storage is full (50 MB). Delete some images before uploading more.

POST /media

media_too_large413

The image is over 8 MB.

what to doResize or recompress it and upload again.

  • An image may be at most 8 MB.

POST /media

unsupported_content_type415

POST /post/{connectionSlug} got a Content-Type other than text/plain or application/x-www-form-urlencoded, or POST /media got something that is not image/png, image/jpeg, image/gif or image/webp.

what to doOn the one-liner, send text/plain or leave the header off; if you want to send JSON, use POST /posts. On an upload, send the image type.

  • Content-Type must be one of image/png, image/jpeg, image/gif, image/webp.
  • Unsupported Content-Type. Use text/plain or omit the header.

POST /media · POST /post/{connectionSlug}

platform_rejected422200

The platform refused the post: a duplicate, a policy violation, or something else on their side.

what to doChange the text and post again. This body carries a req_id: quote it if the rejection looks wrong.

  • LinkedIn: duplicate post was detected
  • X: duplicate post was detected
  • telegram would not delete this post. It may be gone already, or too old to delete.

POST /post/{connectionSlug} · POST /posts · DELETE /posts/{postId}

too many requests429

More than 60 requests in a minute for this API key and endpoint group. This is the one error body with no message field.

what to doSleep until x-ratelimit-reset and retry. On POST /posts, reuse the same Idempotency-Key so the retry cannot double-post.

GET /connections · GET /connections/{connectionId} · PATCH /connections/{connectionId} · GET /connections/{connectionId}/queue · PATCH /connections/{connectionId}/queue · POST /profiles · GET /profiles · GET /profiles/{profileId} · PATCH /profiles/{profileId} · DELETE /profiles/{profileId} · POST /profiles/{profileId}/connect · POST /profiles/{profileId}/keys · GET /profiles/{profileId}/keys · DELETE /profiles/{profileId}/keys/{keyId} · GET /keys · DELETE /keys/{keyId} · GET /connect-sessions/{connectSessionId} · POST /media · GET /media/{mediaId} · DELETE /media/{mediaId} · GET /media/{mediaId}/raw · POST /post/{connectionSlug} · POST /posts · GET /posts · GET /posts/{postId} · DELETE /posts/{postId} · PATCH /posts/{postId} · POST /posts/{postId}/publish · GET /posts/{postId}/metrics · GET /analytics · POST /webhooks · GET /webhooks · DELETE /webhooks/{webhookId} · GET /webhooks/{webhookId}/deliveries · POST /webhooks/{webhookId}/deliveries/{deliveryId}/replay · POST /webhooks/{webhookId}/test

internal_server_error500

yeetpost or the platform's answer broke mid-send; the post may have gone out.

what to doCheck GET /posts/{postId} or the platform itself before resending. A retry with the same Idempotency-Key replays the stored result rather than double posting.

  • An unexpected error occurred

GET /connections · GET /connections/{connectionId} · PATCH /connections/{connectionId} · GET /connections/{connectionId}/queue · PATCH /connections/{connectionId}/queue · POST /profiles · GET /profiles · GET /profiles/{profileId} · PATCH /profiles/{profileId} · DELETE /profiles/{profileId} · POST /profiles/{profileId}/connect · POST /profiles/{profileId}/keys · GET /profiles/{profileId}/keys · DELETE /profiles/{profileId}/keys/{keyId} · GET /keys · DELETE /keys/{keyId} · GET /connect-sessions/{connectSessionId} · POST /media · GET /media/{mediaId} · DELETE /media/{mediaId} · GET /media/{mediaId}/raw · POST /post/{connectionSlug} · POST /posts · GET /posts · GET /posts/{postId} · DELETE /posts/{postId} · PATCH /posts/{postId} · POST /posts/{postId}/publish · GET /posts/{postId}/metrics · GET /analytics · POST /webhooks · GET /webhooks · DELETE /webhooks/{webhookId} · GET /webhooks/{webhookId}/deliveries · POST /webhooks/{webhookId}/deliveries/{deliveryId}/replay · POST /webhooks/{webhookId}/test

platform_unavailable503200

The platform itself answered with a 429 or a 5xx. Nothing was posted.

what to doResend with a new Idempotency-Key.

  • Discord is rate limiting this account, so nothing went out. Send it again later.

POST /post/{connectionSlug} · POST /posts

#The 402 pair

Both 402s are about X, which bills a flat fee per post: 2 cents, or 25 cents with a link. payment_method_required means there is no card on the account; spend_cap_reached means you hit a cap you set yourself. Posts to LinkedIn are free and are never blocked by either.

The detail is on plans and X fees.

#What is safe to retry

  • 429 and 500: retry with backoff.
  • 402 and 403: retrying changes nothing until you add a payment method, raise the cap, upgrade, free a queue slot, reconnect the account, or send the call on an unscoped key.
  • 400, 401, 404, 409, 413, 415 and 422: fix the request first.
  • A draft that was refused with queue_empty, queue_full, payment_method_required, spend_cap_reached or fair_use_reached is still a draft, so publishing the same id again is the retry.
  • On POST /posts, any retry should carry the same Idempotency-Key.