Relay
DocsLog inStart free

REST API

Schedule, publish, and inspect Instagram and TikTok posts over plain HTTP. JSON in, JSON out, one bearer token. Every capability here is also exposed to agents through the MCP server.

Base URL & versioning

https://api.relay.lantean.tech/v1

All endpoints live under the /v1 prefix and take and return application/json. Changes within v1 are additive: new fields may appear in responses, new optional request fields may be accepted. Parse responses tolerantly and ignore unknown keys.

Field naming is not uniform yet. Post, media, and target objects use snake_case; GET /v1/accounts returns camelCase because it serializes database columns directly. Both are shown verbatim below.

Authentication

Every request carries an API key as a bearer token. Create keys in the dashboard under API keys; the full value is displayed once, at creation.

Authorization: Bearer rly_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • Keys look like rly_live_ plus 32 random base62 characters (~190 bits of entropy).
  • Only a SHA-256 hash of the key is stored. Relay cannot show you a key again — rotate by creating a new one and revoking the old.
  • One key authorizes both this API and the MCP server, and inherits every account its owner has connected.
  • Revocation is immediate. Each key records a lastUsedAt timestamp so you can spot keys that are safe to retire.
  • Send the key in the header only — never in a query string, where it would land in logs and referrers.

A missing, malformed, revoked, or unknown key returns 401 with a WWW-Authenticate: Bearer header:

{ "error": "Missing or malformed API key" }   // no Bearer prefix, or not rly_live_*
{ "error": "Invalid API key" }                // well-formed but unknown or revoked

Rate limits

ScopeLimitResponse when exceeded
Per API key120 requests / minute429 with Retry-After
Per user (all keys combined)300 requests / minute429 with Retry-After
Failed auth per IP30 attempts / minute429 with Retry-After

On the MCP endpoint, every JSON-RPC message in a batched request counts against these budgets individually — a batch of 20 tool calls costs 20, and batches are capped at 20 messages per request.

HTTP/1.1 429 Too Many Requests
Retry-After: 37

{ "error": "Rate limit exceeded (120 requests/minute per API key)" }

Limits use a fixed 60-second window, so a burst straddling a window boundary can briefly pass up to twice the limit — treat the number as a floor, not a precise budget. Back off for the number of seconds in Retry-After rather than retrying immediately.

POST /v1/uploads is charged against a second, separate budget: 512 MB per minute per user, on top of the request counts above. Counting requests alone would let a 100 MB endpoint move tens of gigabytes a minute. Exceeding it returns the same 429 and Retry-After contract with its own message, so match on the status rather than the text:

HTTP/1.1 429 Too Many Requests
Retry-After: 41

{ "error": "Upload limit exceeded (512MB/minute per user)" }
Relay's request limit is separate from Instagram's publishing quota of 100 posts per rolling 24 hours per account. Hitting Instagram's quota does not produce an HTTP error — see Publishing quota.

Idempotency

POST /v1/posts and POST /v1/media accept an Idempotency-Key header so a retry after a timeout can't create a duplicate post or a second copy of an asset.

Idempotency-Key: fall-drop-2026-08-04
  • Keys are scoped to (your account, endpoint, key) and remembered for 24 hours.
  • A replayed request returns the original response body and status, plus Idempotency-Replayed: true.
  • Only successful responses are stored. A request that failed validation can be corrected and retried with the same key.
  • Maximum length is 255 characters; longer keys are rejected with 400.
  • Two genuinely simultaneous requests with the same key may both execute — the window is milliseconds wide. Serialize retries rather than firing them in parallel.
  • Use a key derived from your own domain object (campaign ID, content hash), not a random UUID generated per attempt — a fresh UUID per retry defeats the mechanism.
  • POST /v1/posts/:id/retry and DELETE /v1/posts/:id ignore the header — their status guards make an accidental duplicate a safe 400 (the post is already scheduled or cancelled), not a second execution.
  • POST /v1/uploads ignores it too, for a different reason: replays are remembered for 24 hours but a staged file is deleted within the hour, so a replay would hand back a URL that has already 404'd. Re-upload instead — a duplicate staged file costs nothing and expires on its own.

Pagination

GET /v1/posts is cursor-paginated. Pass limit (1–100, default 20) and read next_cursor from the response; when it is null you have the last page.

curl "https://api.relay.lantean.tech/v1/posts?status=scheduled&limit=50" \
  -H "Authorization: Bearer $RELAY_API_KEY"

# then, while next_cursor is not null:
curl "https://api.relay.lantean.tech/v1/posts?status=scheduled&limit=50&cursor=MjAyNi0wNy0zMFQxMTo0..." \
  -H "Authorization: Bearer $RELAY_API_KEY"

The cursor is an opaque keyset over (created_at, id) descending — stable while posts are being created, unlike offset paging. Treat it as an opaque string; a cursor you didn't get from a previous page is rejected with 400.

Errors

StatusMeaningWhat to do
400Validation or domain error — bad post type for the media, unknown account ID, un-editable post, unsupported media format.Fix the request. Safe to retry with the same idempotency key.
401Missing, malformed, unknown, or revoked API key.Check the header; rotate the key if it was revoked.
404No such post, or it belongs to another account.Do not retry.
411POST /v1/uploads only — no Content-Length. It is required so the upload byte budget can be charged before the body is read.Send the exact body size and retry.
413POST /v1/uploads only — the body is over the cap for its type (8 MB image, 100 MB video).Do not retry. Compress or re-encode first.
429Rate limited.Sleep for Retry-After seconds, then retry.
5xxTransient server-side failure.Retry with backoff and the same idempotency key.

Two error body shapes

Domain errors — the ones you'll actually read — are a single message:

{ "error": "Feed posts take exactly one image — use post_type 'reel' for a single video or 'carousel' for multiple items" }

Requests that fail schema validation before reaching the handler (a missing required field, a caption over 2,200 characters, a non-ISO timestamp) return the validator's own output instead, where error.message is a JSON-encoded string of issues:

{
  "success": false,
  "error": {
    "name": "ZodError",
    "message": "[{\"code\":\"too_small\",\"minimum\":1,\"path\":[\"caption\"],\"message\":\"Too small: expected string to have >=1 characters\"}]"
  }
}

Handle both: read error when it is a string, otherwise parse error.message.

Publish-time failures are not HTTP errors

Creating a post only queues it. Anything Instagram rejects later — a revoked token, a video that fails transcoding, a quota ceiling — surfaces on the post's per-target timeline, not on the original HTTP call. Poll GET /v1/posts/:id and read targets[].error; the codes are listed in the failure reference.

There are no webhooks yet. To track a publish, poll the post — every ~30 seconds is plenty, since the scheduler itself runs once a minute.

Endpoints

GET/v1/accounts
List connected social accounts, newest connection first. The ids returned here are what post targets refer to.
curl https://api.relay.lantean.tech/v1/accounts \
  -H "Authorization: Bearer $RELAY_API_KEY"
200 OK
{
  "accounts": [
    {
      "id": "9d1f2c34-5b6a-4c8d-9e0f-1a2b3c4d5e6f",
      "platform": "instagram",
      "username": "northlight.co",
      "displayName": "Northlight",
      "status": "connected",
      "tokenExpiresAt": "2026-09-21T10:02:11.000Z",
      "connectedAt": "2026-07-23T10:02:11.000Z",
      "followersCount": 12840,
      "mediaCount": 318,
      "statsSyncedAt": "2026-07-30T05:30:00.000Z",
      "canComment": true,
      "canDirectPost": false,
      "canUpload": false
    }
  ]
}

platform is instagram or tiktok. status is connected, expiring, reconnect_required, or disconnected. Only the first two can be used as post targets. Follower and media counts refresh once a day. canComment (Instagram) says whether first comments can be posted; canDirectPost and canUpload (TikTok) say whether the account allowed direct posting and sending to the TikTok inbox.

GET/v1/accounts/:id/tiktok/creator-info
What a TikTok account allows right now, asked live from TikTok. Call it before creating a TikTok post: privacy_level must be one of privacy_level_options.
200 OK
{
  "creator": {
    "username": "northlight.co",
    "nickname": "Northlight",
    "avatar_url": "https://p16-sign.tiktokcdn.com/…",
    "privacy_level_options": ["PUBLIC_TO_EVERYONE", "MUTUAL_FOLLOW_FRIENDS", "SELF_ONLY"],
    "comment_disabled": false,
    "duet_disabled": false,
    "stitch_disabled": true,
    "max_video_post_duration_sec": 600,
    "can_post": true,
    "cannot_post_reason": null
  }
}

A disabled interaction is forced off on every post. can_post is false when TikTok is not accepting posts from the account right now — usually its daily cap — with the reason in cannot_post_reason. See TikTok settings.

POST/v1/posts
Create a post and queue it for publishing. Returns 201 with the queued post; delivery happens asynchronously.
FieldTypeDescription
targetsstring[] requiredAccount IDs to post to (from accounts_list), up to 10 Example: ["9d1f…"]
captionstring requiredCaption text, 1–2200 characters (ignored for stories) Example: "Fall drop is live — link in bio."
post_typestring?feed | carousel | reel | story — inferred from media when omitted (stories never inferred) Example: reel
media(string | object)[]Media IDs or public URLs (JPEG images, MP4/MOV videos) — up to 10 when an Instagram account is targeted, up to 35 images for a TikTok-only photo post. Required unless draft is true, since neither platform has text-only posts. For a per-slide caption on an Instagram carousel, pass { id, caption } or { url, caption } instead of a bare string — exactly one of id or url Example: ["https://cdn.example.com/fall.jpg", { "id": "a1b2…", "caption": "Sand, our best seller." }]
share_to_feedboolean?Reels only: also show the reel on the profile grid (Instagram defaults to true) Example: false
first_commentstring?Text posted as a comment on the post the moment it goes live, 1–2200 characters — the usual home for a hashtag block you want out of the caption. Posted once per Instagram target account (TikTok has no first comments, so it needs at least one Instagram target). Not valid on stories, which have no comments Example: "#fallcollection #newin #slowfashion"
tiktokobject?TikTok settings, required when any target is a TikTok account: { mode: "direct" | "inbox" (default direct), privacy_level: PUBLIC_TO_EVERYONE | MUTUAL_FOLLOW_FRIENDS | FOLLOWER_OF_CREATOR | SELF_ONLY (required for direct, no default), allow_comment, allow_duet, allow_stitch (all default false; duet/stitch are video only), brand_organic (promotes your own brand), brand_content (paid partnership — never SELF_ONLY), is_aigc, caption (TikTok-only caption override), title (photo posts, ≤90), cover_timestamp_ms (video), photo_cover_index (photo), auto_add_music (photo) } Example: { "privacy_level": "PUBLIC_TO_EVERYONE", "allow_comment": true }
schedule_atstring?ISO 8601 timestamp with offset — omit to publish now Example: "2026-08-04T18:30:00Z"
draftboolean?Save as a draft instead of queueing it — nothing publishes until the user approves it on the preview page Example: true

Media entries are either media IDs from POST /v1/media or public https URLs, which Relay fetches and stores on the spot — including the short-lived URLs handed back by POST /v1/uploads, which is how you post a file you hold locally. Order is preserved — the first item is the carousel cover.

Per-slide captions: a carousel slide can carry its own caption. Replace the bare string with { "id": "…", "caption": "…" } for a stored media ID or { "url": "…", "caption": "…" } for a public link — exactly one of id or url, and the caption is 1–2,200 characters. Instagram shows that text on that slide; the post's own caption still applies to the whole post. Mix bare strings and objects freely — a string is simply a slide with no caption of its own. Per-item captions are carousel-only: send one on a feed post, reel, or story and the request is rejected.

Type inference: omit post_type and Relay derives it from the media — one image is a feed post, one video is a reel, two or more is a carousel. Stories are never inferred; ask for them explicitly.

Scheduling: omit schedule_at to publish on the next scheduler tick (under a minute). A past timestamp behaves the same way. Posts created through this API always come back with status scheduled — drafts are a dashboard concept.

curl -X POST https://api.relay.lantean.tech/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: fall-drop-2026-08-04" \
  -d '{
    "targets": ["9d1f2c34-5b6a-4c8d-9e0f-1a2b3c4d5e6f"],
    "caption": "Fall drop is live — link in bio.",
    "media": ["https://cdn.example.com/fall-drop.jpg"],
    "schedule_at": "2026-08-04T18:30:00Z"
  }'
201 Created
{
  "post": {
    "id": "3f0c8a71-2d64-4b18-9a55-7c2e11d0f4ab",
    "caption": "Fall drop is live — link in bio.",
    "status": "scheduled",
    "post_type": "feed",
    "share_to_feed": null,
    "schedule_at": "2026-08-04T18:30:00.000Z",
    "published_at": null,
    "source": "api",
    "created_at": "2026-07-30T12:41:09.220Z",
    "media": [
      {
        "id": "a1b2c3d4-0000-4a1b-8c2d-3e4f5a6b7c8d",
        "url": "https://media.relay.lantean.tech/users/…/a1b2….jpg",
        "kind": "image",
        "content_type": "image/jpeg",
        "size_bytes": 412903,
        "width": 1080,
        "height": 1350,
        "created_at": "2026-07-30T12:41:08.911Z",
        "caption": null
      }
    ],
    "targets": [
      {
        "id": "b7e1…",
        "account_id": "9d1f2c34-5b6a-4c8d-9e0f-1a2b3c4d5e6f",
        "username": "northlight.co",
        "platform": "instagram",
        "status": "pending",
        "platform_post_id": null,
        "permalink": null,
        "published_at": null,
        "error": null
      }
    ]
  }
}

More examples

Reel, kept off the profile grid
{
  "targets": ["9d1f…"],
  "caption": "Behind the drop.",
  "post_type": "reel",
  "media": ["https://cdn.example.com/behind.mp4"],
  "share_to_feed": false
}
Carousel from previously uploaded media
{
  "targets": ["9d1f…"],
  "caption": "Every colourway.",
  "media": ["a1b2c3d4-…", "e5f6a7b8-…", "c9d0e1f2-…"]
}
Carousel with a caption on each slide
{
  "targets": ["9d1f…"],
  "caption": "Every colourway.",
  "media": [
    { "id": "a1b2c3d4-…", "caption": "Sand, our best seller." },
    { "url": "https://cdn.example.com/slate.jpg", "caption": "Slate — new this season." },
    "c9d0e1f2-…"
  ]
}
Story (Instagram shows no caption on stories)
{
  "targets": ["9d1f…"],
  "caption": "internal note — not shown",
  "post_type": "story",
  "media": ["https://cdn.example.com/story.jpg"]
}
Fan out to several accounts
{
  "targets": ["9d1f…", "4a7b…"],
  "caption": "Same drop, two brands.",
  "media": ["a1b2c3d4-…"]
}
GET/v1/posts
List posts newest-first, with their media and per-account targets.
Query paramTypeDescription
statusstring?draft | scheduled | publishing | published | partly | failed | cancelled
limitnumber?1–100, default 20
cursorstring?next_cursor from a previous page
200 OK
{
  "posts": [ /* post objects, newest first */ ],
  "next_cursor": "MjAyNi0wNy0zMFQxMTo0Mzoy…"
}
GET/v1/posts/:id
Fetch one post with its per-target delivery timeline — the endpoint to poll after creating a post.
200 OK — one target live, one rejected
{
  "post": {
    "id": "3f0c8a71-…",
    "status": "partly",
    "post_type": "feed",
    "published_at": "2026-08-04T18:30:42.180Z",
    "targets": [
      {
        "account_id": "9d1f…",
        "username": "northlight.co",
        "status": "published",
        "platform_post_id": "17912345678901234",
        "permalink": "https://www.instagram.com/p/C9xY…/",
        "published_at": "2026-08-04T18:30:42.180Z",
        "error": null
      },
      {
        "account_id": "4a7b…",
        "username": "northlight.eu",
        "status": "failed",
        "platform_post_id": null,
        "permalink": null,
        "published_at": null,
        "error": {
          "code": "auth_reconnect_required",
          "message": "Instagram authorization expired or was revoked — reconnect the account"
        }
      }
    ]
  }
}

Returns 404 with { "error": "Post not found" } for an unknown ID or one owned by a different account.

PATCH/v1/posts/:id
Edit an unpublished post: caption, schedule_at, or both. At least one is required.
curl -X PATCH https://api.relay.lantean.tech/v1/posts/3f0c8a71-… \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "schedule_at": "2026-08-05T09:00:00Z" }'

Only draft and scheduled posts can be edited — once the scheduler claims a post it is frozen, and you get 400 with Only draft/scheduled posts can be edited. Media, per-slide captions, and targets are immutable on this API — caption here is the post's own caption, not a carousel slide's. To change any of them, cancel and create a new post instead (the dashboard can edit them directly).

POST/v1/posts/:id/retry
Re-queue a failed or partly-published post for another attempt. Returns the re-queued post.
curl -X POST https://api.relay.lantean.tech/v1/posts/3f0c8a71-…/retry \
  -H "Authorization: Bearer $RELAY_API_KEY"

Only failed and partly posts can be retried — anything else returns 400. Failed targets reset to pending and the post re-enters the publish queue on the next scheduler tick; targets that already published are untouched, so a partly-published post republishes only what failed. The post keeps its ID. Fix the underlying cause first (the per-target error on GET /v1/posts/:id says why Instagram said no) or the retry will fail the same way.

DELETE/v1/posts/:id
Cancel a draft or scheduled post before it publishes. Returns the cancelled post.

Cancellation is atomic against the scheduler: if a publish has already started you get 400 rather than a half-cancelled post. It cannot remove something already live on Instagram — delete that in the Instagram app.

POST/v1/media
Store a file in Relay — from a public URL or inline base64 — and return a reusable media ID.
curl -X POST https://api.relay.lantean.tech/v1/media \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: fall-drop-hero-v3" \
  -d '{ "url": "https://cdn.example.com/fall-drop.jpg" }'
Inline upload (base64 or data: URI)
curl -X POST https://api.relay.lantean.tech/v1/media \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{ \"data\": \"$(base64 -w0 fall-drop.jpg)\", \"content_type\": \"image/jpeg\" }"
201 Created
{
  "media": {
    "id": "a1b2c3d4-0000-4a1b-8c2d-3e4f5a6b7c8d",
    "url": "https://media.relay.lantean.tech/users/…/a1b2….jpg",
    "kind": "image",
    "content_type": "image/jpeg",
    "size_bytes": 412903,
    "width": 1080,
    "height": 1350,
    "created_at": "2026-07-30T12:41:08.911Z"
  }
}
  • Pass exactly one of url (a publicly reachable http(s) URL Relay fetches) or data (the file content itself, as raw base64 or a data: URI). With data, the type comes from content_type, the data: URI, or the bytes themselves, in that order.
  • Accepted types: image/jpeg (≤ 8 MB), video/mp4 and video/quicktime (≤ 100 MB). PNG and other image formats are rejected here rather than at publish time, because Instagram will not accept them — convert before uploading. (The dashboard does this conversion in the browser, which is why it accepts any image you give it.)
  • Image dimensions are parsed on upload so aspect-ratio problems surface now, not when the post goes out.
  • The source URL must be publicly reachable over http(s). Private and link-local hosts are refused. For video over ~50 MB use url — base64 inflates payloads ~33% and oversized requests are rejected.
  • Uploading is optional — POST /v1/posts accepts URLs directly. Upload first when you want to reuse one asset across posts or validate it before composing.
POST/v1/uploads
Park a local file somewhere public and get a short-lived URL back, so you have something to pass as a media entry.
curl -X POST https://api.relay.lantean.tech/v1/uploads \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: image/jpeg" \
  --data-binary @fall-drop.jpg
201 Created
{
  "upload": {
    "url": "https://api.relay.lantean.tech/v1/uploads/users/…/a1b2….jpg",
    "kind": "image",
    "content_type": "image/jpeg",
    "size_bytes": 412903,
    "expires_at": "2026-07-30T13:41:08.911Z"
  }
}
Then use it like any other URL
curl -X POST https://api.relay.lantean.tech/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "targets": ["3f0c8a71-…"], "caption": "Fall drop", "media": ["<the url above>"] }'
  • The request body is the file itself, not JSON — unlike every other endpoint here. Content-Type identifies the format and Content-Length is required.
  • The URL expires. Staged files are deleted an hour after upload, and the URL 404s from that moment. This is a hand-off, not storage: create the post while the URL is alive and it keeps a permanent copy of the bytes, unaffected by the expiry — so scheduling a post weeks out is fine. For an asset you want to reuse across posts, use POST /v1/media instead.
  • Same accepted types and size caps as POST /v1/media: image/jpeg (≤ 8 MB), video/mp4 and video/quicktime (≤ 100 MB).
  • Reach for this when you hold the bytes and have nowhere public to put them. It avoids base64 entirely, which matters most for video — inline data inflates the payload by about a third.
  • Uploads are charged against a 512 MB/minute budget per user, on top of the request rate limits. Over it you get a 429 with Retry-After. Idempotency-Key is not accepted here — replays are honoured for 24 h but a staged file is gone within the hour, so retrying the upload is the right move.

Object reference

Post

FieldTypeDescription
idstringUUID.
captionstringCaption text, 1–2,200 characters.
statusstringRoll-up across targets — see status reference.
post_typestring | nullfeed, carousel, reel, or story. Null only for a media-less draft.
share_to_feedboolean | nullReels only. Null means Instagram’s default (shown on the grid).
tiktokobject | nullTikTok settings applied to every TikTok target (mode, privacy_level, interactions, disclosure). Null when the post has no TikTok target. See TikTok settings.
schedule_atstring | nullISO 8601. When the scheduler becomes eligible to publish it.
published_atstring | nullISO 8601. Set when the first target goes live.
sourcestringdashboard, api, or mcp — where the post was created.
created_atstringISO 8601.
mediaMedia[]Ordered media items. On a carousel each carries its own caption.
targetsTarget[]One per account.

Target

FieldTypeDescription
idstringUUID of the post↔account row.
account_idstringThe connected account.
usernamestringHandle at publish time.
platformstringinstagram or tiktok.
statusstringpending, uploading, processing, published, failed, cancelled.
platform_post_idstring | nullThe platform’s post ID once published (TikTok: once public).
permalinkstring | nullPublic post URL. On TikTok it can arrive a little after published, once TikTok’s review finishes; SELF_ONLY posts have none.
published_atstring | nullISO 8601.
error{ code, message } | nullPopulated on failure, and occasionally as a non-fatal breadcrumb (for example quota_exceeded while waiting, or tiktok_inbox on a published target that was delivered to the TikTok inbox).

Media

FieldTypeDescription
idstringUUID — pass this in post media.
urlstringPublic URL Instagram and TikTok fetch from.
kindstringimage or video.
content_typestringimage/jpeg, video/mp4, video/quicktime.
size_bytesnumber | nullStored size.
widthnumber | nullPixels. Parsed for JPEGs; null for video.
heightnumber | nullPixels. Parsed for JPEGs; null for video.
duration_msnumber | nullVideo length, read from the MP4/MOV header at upload (TikTok checks it against the creator’s limit). Null for images.
created_atstringISO 8601.
captionstring | nullThis slide's own caption. Carousels only; null elsewhere and on uncaptioned slides.

caption appears only on media inside a post. The media-library object — what POST /v1/media and the MCP media_upload tool return — has no caption, because a caption belongs to a post's use of an asset, not to the asset itself. The same photo can carry different captions in two different carousels.

Account

Returned by GET /v1/accounts in camelCase: id, platform, username, displayName, status, tokenExpiresAt, connectedAt, followersCount, mediaCount, statsSyncedAt. The MCP accounts_list tool returns the same account in snake_case with a derived health and token_expires_in_days.

Status reference

Post status

StatusMeaning
draftCreated in the dashboard without a schedule. The API never returns a draft it created.
scheduledQueued. The scheduler will pick it up at schedule_at.
publishingClaimed by the scheduler; at least one target is still in flight. No longer editable.
publishedEvery target is live.
partlySome targets are live, others failed. Inspect targets[] to see which.
failedEvery target failed.
cancelledCancelled before publishing.

Target status

StatusMeaning
pendingNot started.
uploadingCarousel children are being created and transcoded.
processingThe media container exists; waiting for Instagram to finish processing it.
publishedLive, with a platform_post_id and usually a permalink.
failedTerminal. Read error.code.
cancelledThe post was cancelled before this target ran.
© 2026 Relaysupport@lantean.techPrivacyTerms