Posts

One endpoint publishes to every provider, but they behave differently enough to be worth reading separately. Instagram publishes synchronously and returns 201, and so does X. TikTok and YouTube publish asynchronously and return 202 with an id you poll. Facebook does both, decided by the media: a photo or multi-photo post returns 201 with the finished post, while a Reel or feed video returns 202.

Editing and deleting posts are deliberately not part of the public API in v1, except deleting an X post.

GET/api/v1/postsList posts

Lists posts from the Instagram accounts, Facebook Pages, TikTok accounts, and YouTube channels in your key's profile, newest first. Passing platform=whatsapp returns 422 unsupported_platform. Uses the aggregate response contract, and follows each provider's pagination internally. TikTok is read 20 posts per request and rate-limited per account, so its listing stops at the 60 most recent posts. YouTube reads cost quota shared by every Adeli customer, so its listing is the 50 most recent videos per channel. X bills every post it returns (Usage), so X is listed only when you ask for it with platform=x or an X accountId, and then only the 10 most recent posts.

Query parameters

platform"instagram" | "facebook" | "tiktok" | "youtube" | "x"
Optional. Narrow to one provider. X is listed only when asked for.
accountIduuid
Narrow to one connected account.
profileIduuid
Defaults to your key's profile.
json
{
  "status": "complete",
  "posts": [
    {
      "id": "instagram_post_00000000-0000-0000-0000-000000000000_17900000000000000",
      "platform": "instagram",
      "accountId": "00000000-0000-0000-0000-000000000000",
      "profileId": "00000000-0000-4000-8000-000000000001",
      "providerId": "17900000000000000",
      "caption": "Hello",
      "media": [{ "type": "IMAGE", "url": "https://...", "thumbnailUrl": "https://..." }],
      "permalink": "https://www.instagram.com/p/...",
      "engagement": { "likes": 12, "comments": 3 },
      "publishedAt": "2026-04-01T12:00:00.000Z"
    },
    {
      "id": "tiktok_post_00000000-0000-0000-0000-000000000002_7300000000000000000",
      "platform": "tiktok",
      "accountId": "00000000-0000-0000-0000-000000000002",
      "profileId": "00000000-0000-4000-8000-000000000001",
      "providerId": "7300000000000000000",
      "caption": "Backyard birds",
      "media": [{ "type": "VIDEO", "url": "https://www.tiktok.com/player/v1/7300000000000000000", "thumbnailUrl": "https://p16-sign-va.tiktokcdn.com/..." }],
      "permalink": "https://www.tiktok.com/@creator/video/7300000000000000000",
      "engagement": { "likes": 30, "comments": 6, "shares": 4, "views": 2400, "reach": 1900 },
      "publishedAt": "2026-03-30T12:00:00.000Z"
    }
  ],
  "errors": []
}

TikTok posts add shares, views, and reach to engagement, and their media[].url is TikTok's embeddable player. For watch time and retention, pass the providerId to TikTok post insights.

YouTube videos add views to engagement; caption is the video's title and media[].url its watch page. Pass the providerId to YouTube video insights for watch time.

X posts add shares (reposts) and views (impressions) to engagement; comments counts replies, and caption is the post's text.

Errors — 401 unauthorized, 400 invalid_request, 422 unsupported_platform, 404 profile_not_found, 404 account_not_found, 502 provider_error.


Publishing to Instagram

POST/api/v1/postsPublish an Instagram image or carousel

Send application/json with base64 media. Images may be JPEG, PNG, or WebP, and each must decode to no more than 8 MiB. Adeli normalizes them to JPEG and stages them where Instagram can fetch them.

Body

platform"instagram"Required
accountIduuidRequired
A connected Instagram account in your key's profile.
profileIduuid
Defaults to your key's profile.
captionstring
Up to 2,200 characters, 30 hashtags, and 20 @ mentions. Hashtags and mentions are written inline — Instagram reads them from the caption.
imageobject
A single image: { contentType, base64, altText?, userTags? }. Mutually exclusive with images.
imagesobject[]
A carousel of 2 to 10 images, each shaped like image. Mutually exclusive with image.
image.altTextstring
Up to 1,000 characters of alt text for screen readers. Per image, carousels included.
image.userTagsobject[]
Up to 20 people tagged in the image, each { username, x, y }. x and y are fractions from 0 to 1 measured from the image's top-left corner. A leading @ is accepted.
collaboratorsstring[]
Up to 3 usernames invited as co-authors. Each must accept the invite in Instagram before the post shows them.
isAiGeneratedboolean
Adds Instagram's AI-generated label to the post.
curl --fail-with-body -X POST "$ADELI_URL/api/v1/posts" \
-H "Authorization: Bearer $ADELI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @post.json

# post.json
# {"platform":"instagram","accountId":"...","caption":"Hello",
#  "image":{"contentType":"image/png","base64":"iVBORw0KGgo..."}}

A carousel swaps image for images. Alt text and people tags belong to each image; the caption, collaborators, and AI label belong to the post:

json
{
  "platform": "instagram",
  "accountId": "00000000-0000-0000-0000-000000000000",
  "caption": "Three backyard birds with @centralparkbirders #birding #spring",
  "collaborators": ["centralparkbirders"],
  "isAiGenerated": false,
  "images": [
    { "contentType": "image/webp", "base64": "...", "altText": "A cardinal on a snowy branch" },
    { "contentType": "image/png", "base64": "...", "userTags": [{ "username": "birder", "x": 0.4, "y": 0.6 }] },
    { "contentType": "image/jpeg", "base64": "..." }
  ]
}

Unknown fields are rejected with 400 invalid_request rather than ignored. A tagged person or collaborator Instagram cannot resolve — a private account or a misspelled username — fails with 422 provider_rejected and Instagram's own explanation as the message.

Success returns 201 and the normalized post, with media mirroring the staged images. engagement is null on a fresh post — the counts are not available until Instagram has them.

Errors — 400 invalid_request, 413 invalid_request, 415 unsupported_media_type, 422 invalid_image, 422 connection_expired, 422 provider_not_configured, 422 provider_rejected, 404 account_not_found, 404 profile_not_found, 502 provider_error.


Publishing to Facebook

POST/api/v1/postsPublish to a Facebook Page

Four shapes, chosen by which media field you send rather than by a kind string, so an impossible combination cannot be expressed. As with Instagram, media is staged on Adeli's HTTPS media host because Meta fetches it by URL.

platform"facebook"Required
accountIduuidRequired
A connected Facebook Page in your key's profile.
messagestring
Up to 2200 characters. The caption shown with the post.
imageobject
One photo — contentType and base64. Publishes synchronously.
imagesobject[]
2–10 photos, published as one multi-photo post. Synchronous.
reelobject
One vertical video, published as a Reel. Asynchronous.
videoobject
One video, published to the Page feed. Asynchronous.
titlestring
Only valid alongside video. Up to 255 characters.

A photo response is the same normalized post Instagram returns, with platform: "facebook". A Reel or feed video returns:

json
{
  "platform": "facebook",
  "accountId": "00000000-0000-0000-0000-000000000000",
  "profileId": "00000000-0000-0000-0000-000000000000",
  "publishId": "1234567890",
  "status": "ACCEPTED",
  "statusUrl": "/api/v1/posts/facebook/status?accountId=...&profileId=...&publishId=1234567890"
}

GET/api/v1/posts/facebook/statusCheck a Facebook video publish

Query parameters

accountIduuidRequired
publishIdstringRequired
From the publish response.
profileIduuid
Defaults to your key's profile.

status is PROCESSING, READY, or ERROR. A READY response carries permalink; an ERROR carries failureReason, which is Meta's own account of what was wrong with the video — usually its length or aspect ratio.

A Page must be chosen first

A Facebook account in pending_page_selection has no Page token and cannot publish. Every write against it returns 409 page_not_selected until your customer picks a Page. See Connect.

Publishing to TikTok

TikTok publishing is asynchronous. A successful request means TikTok accepted the content, not that it is live. Adeli publishes through TikTok API for Business, which pulls every video and photo from a URL: whatever you send — base64, multipart, or a URL — Adeli stages it on its own verified media host first.

Videos are public or drafts

TikTok for Business has no privacy setting for videos. A DIRECT_POST video is published to everyone, so its privacy_level must be PUBLIC_TO_EVERYONE or left out; anything else is 422 privacy_level_unsupported rather than a post that is more public than you asked for. To keep a video private, send post_mode: "MEDIA_UPLOAD": it arrives as a draft in the creator's TikTok inbox. Photo posts accept every privacy_level the creator currently allows.

TikTok allows 15 API posts per account per day.

Check creator capabilities first

GET/api/v1/posts/tiktok/creator-infoGet creator info

Recommended before a Direct Post: TikTok revalidates what the creator currently allows, and those capabilities change. Use the response to build your UI and to pick a legal photo privacy_level. nickname is the account's display name.

Query parameters

accountIduuidRequired
A connected TikTok account.
profileIduuid
Defaults to your key's profile.
json
{
  "platform": "tiktok",
  "accountId": "00000000-0000-0000-0000-000000000000",
  "profileId": "00000000-0000-4000-8000-000000000001",
  "creator": {
    "nickname": "Creator",
    "privacyLevelOptions": ["PUBLIC_TO_EVERYONE", "MUTUAL_FOLLOW_FRIENDS", "SELF_ONLY"],
    "commentDisabled": false,
    "duetDisabled": false,
    "stitchDisabled": false,
    "maxVideoPostDurationSec": 600
  }
}

Publish

POST/api/v1/postsPublish a TikTok video, photo, or carousel

Body

platform"tiktok"Required
accountIduuidRequired
A connected TikTok account.
profileIduuid
Defaults to your key's profile.
post_mode"DIRECT_POST" | "MEDIA_UPLOAD"
Defaults to DIRECT_POST, which publishes. MEDIA_UPLOAD sends a draft to the creator's TikTok inbox to finish in the app; a video draft carries the media only, a photo draft its title and description too.
tiktok_titlestring
Up to 2,200 characters for a video, and up to 90 for photos.
tiktok_descriptionstring
Up to 4,000 characters. Photo posts only.
privacy_levelstring
Videos: PUBLIC_TO_EVERYONE or omitted. Photos: one of PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY that the creator currently allows; defaults to PUBLIC_TO_EVERYONE.
music_usage_confirmedtrueRequired
Must be literally true. You are confirming the creator agreed to TikTok's music usage terms.
disable_commentboolean
Defaults to false.
disable_duetboolean
Video only. Defaults to false.
disable_stitchboolean
Video only. Defaults to false.
cover_timestampinteger
Video only. Milliseconds into the video to use as the cover. Defaults to 1000.
photo_cover_indexinteger
Photos only. Defaults to 0, and must be less than the number of photos.
auto_add_musicboolean
Photos only. Defaults to false.
is_aigcboolean
Marks the content as AI-generated. Defaults to false.
brand_content_toggleboolean
Paid partnership. Requires privacy_level: \"PUBLIC_TO_EVERYONE\" on a Direct Post.
brand_organic_toggleboolean
Promotes the creator's own brand.
videoobject
Either { content_type: "video/mp4", base64 } or { url } pointing at public HTTPS. Mutually exclusive with photos.
photosobject[]
1 to 35 photos, each { content_type, base64 }. Remote photo URLs are not accepted. Mutually exclusive with video.

Exactly one of video or photos

Sending both, or neither, is 400 invalid_request.

curl --fail-with-body -X POST "$ADELI_URL/api/v1/posts" \
-H "Authorization: Bearer $ADELI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "platform": "tiktok",
  "accountId": "'"$ACCOUNT_ID"'",
  "post_mode": "MEDIA_UPLOAD",
  "tiktok_title": "Bird video",
  "music_usage_confirmed": true,
  "video": { "url": "https://media.example/video.mp4" }
}'

A video published publicly:

json
{
  "platform": "tiktok",
  "accountId": "00000000-0000-0000-0000-000000000000",
  "tiktok_title": "Morning chorus #birds",
  "privacy_level": "PUBLIC_TO_EVERYONE",
  "disable_duet": false,
  "disable_stitch": true,
  "music_usage_confirmed": true,
  "video": { "url": "https://media.example/video.mp4" }
}

A photo carousel posted directly, visible only to the creator:

json
{
  "platform": "tiktok",
  "accountId": "00000000-0000-0000-0000-000000000000",
  "post_mode": "DIRECT_POST",
  "tiktok_title": "Backyard birds",
  "tiktok_description": "Cardinal, Titmouse, and Robin",
  "privacy_level": "SELF_ONLY",
  "photo_cover_index": 0,
  "auto_add_music": false,
  "music_usage_confirmed": true,
  "photos": [
    { "content_type": "image/webp", "base64": "..." },
    { "content_type": "image/jpeg", "base64": "..." }
  ]
}

Multipart uploads

For large videos, send multipart/form-data instead. Scalar fields use the same names, booleans must be the strings "true" or "false", and the media is either one video file or 1–35 repeated photos[] files. Multipart is TikTok only.

bash
curl --fail-with-body -X POST "$ADELI_URL/api/v1/posts" \
  -H "Authorization: Bearer $ADELI_API_KEY" \
  -F 'platform=tiktok' \
  -F "accountId=$ACCOUNT_ID" \
  -F 'post_mode=DIRECT_POST' \
  -F 'privacy_level=PUBLIC_TO_EVERYONE' \
  -F 'tiktok_title=Morning chorus' \
  -F 'music_usage_confirmed=true' \
  -F 'video=@video.mp4;type=video/mp4'
bash
curl --fail-with-body -X POST "$ADELI_URL/api/v1/posts" \
  -H "Authorization: Bearer $ADELI_API_KEY" \
  -F 'platform=tiktok' \
  -F "accountId=$ACCOUNT_ID" \
  -F 'post_mode=MEDIA_UPLOAD' \
  -F 'music_usage_confirmed=true' \
  -F 'photos[]=@cardinal.webp;type=image/webp' \
  -F 'photos[]=@robin.jpg;type=image/jpeg'

The response

Success returns 202:

json
{
  "platform": "tiktok",
  "accountId": "00000000-0000-0000-0000-000000000000",
  "profileId": "00000000-0000-4000-8000-000000000001",
  "publishId": "v_pub_url~v1.2345123456789123456",
  "status": "ACCEPTED",
  "statusUrl": "/api/v1/posts/tiktok/status?accountId=...&profileId=...&publishId=...",
  "warnings": [
    { "code": "video_draft_media_only", "message": "TikTok video drafts receive media only; post settings must be completed in TikTok" }
  ]
}

statusUrl is a relative path

Join it to your base URL before requesting it. warnings is present only when non-empty — inbox uploads report the settings TikTok ignored, since a video sent to the inbox carries media and nothing else.

Poll for the result

GET/api/v1/posts/tiktok/statusGet publish status

Query parameters

accountIduuidRequired
publishIdstringRequired
From the publish response. 1–200 characters of [A-Za-z0-9._~-].
profileIduuid
Defaults to your key's profile.
json
{
  "platform": "tiktok",
  "accountId": "00000000-0000-0000-0000-000000000000",
  "profileId": "00000000-0000-4000-8000-000000000001",
  "publishId": "v_pub_url~v1.2345123456789123456",
  "status": "PUBLISH_COMPLETE",
  "publicPostId": "7300000000000000000",
  "publicPostIds": ["7300000000000000000"],
  "failureReason": null,
  "publiclyAvailable": true,
  "source": "webhook"
}

Fields

statusstring
PROCESSING_DOWNLOAD while TikTok fetches the media; then PUBLISH_COMPLETE, SEND_TO_USER_INBOX (a draft was delivered), or FAILED. The last three are final.
publicPostIdsstring[]
The TikTok post ids, once the post is public. They can arrive up to a few minutes after PUBLISH_COMPLETE, so keep polling until they do. Absent for posts that are not public.
failureReasonstring | null
TikTok's machine-readable reason when FAILED, such as frame_rate_check_failed, video_pull_failed, or spam_risk_too_many_posts (the daily limit).
publiclyAvailableboolean | null
Whether the post is publicly viewable. false after TikTok reports it is no longer public — moderation, or the creator made it private. null when not yet known.
source"webhook" | "provider"
Whether the answer came from a TikTok webhook Adeli received or a live status call.

Publishing to YouTube

POST/api/v1/postsUpload a YouTube video

YouTube takes one video per post. There is no separate Shorts endpoint: a vertical or square video up to three minutes long becomes a Short. Send application/json with a public HTTPS video.url (Adeli downloads it, up to 1 GB) or base64, or upload the file itself with multipart.

Uploads are private until Adeli passes YouTube's audit

YouTube locks every video uploaded through an app to private until that app passes the YouTube API Services audit. Until Adeli does, ask for any visibility you like: the response reports what YouTube actually applied in appliedPrivacy, and privacyRestricted is true when that is not what you asked for. Change the visibility in YouTube Studio afterwards.

Body

platform"youtube"Required
accountIduuidRequired
A connected YouTube channel in your key's profile.
profileIduuid
Defaults to your key's profile.
titlestringRequired
1–100 characters, with no < or >.
privacy_status"public" | "unlisted" | "private"Required
Who can see the video. There is no default.
made_for_kidsbooleanRequired
Whether the video is made for kids, as YouTube requires under COPPA. This is the channel owner's own declaration: ask them, never guess.
videoobjectRequired
{ url }, a public HTTPS URL of up to 1 GB, or { content_type, base64 } with video/mp4, video/quicktime, or video/webm.
descriptionstring
Up to 5,000 bytes of UTF-8, with no < or >.
tagsstring[]
Up to 500 characters in total, counting the commas YouTube puts between them.
category_idstring
A numeric YouTube category id. Defaults to 22, People & Blogs.
contains_synthetic_mediaboolean
Discloses realistic altered or synthetic content.
publish_atstring
An ISO 8601 time in the future. Requires privacy_status: "private": YouTube makes the video public at that time.
notify_subscribersboolean
Defaults to true.
bash
curl --fail-with-body -X POST "$ADELI_URL/api/v1/posts" \
  -H "Authorization: Bearer $ADELI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "youtube",
    "accountId": "'"$ACCOUNT_ID"'",
    "title": "Heron fishing at dawn",
    "privacy_status": "public",
    "made_for_kids": false,
    "video": { "url": "https://cdn.example/heron.mp4" }
  }'

YouTube multipart uploads

POST/api/v1/posts/youtubeUpload a YouTube video file

POST /api/v1/posts takes multipart for TikTok only, so a YouTube file upload has its own route. Fields use the same names; booleans are the strings "true" or "false", and tags is comma-separated. Send one video file of up to 4 GiB and, optionally, a thumbnail (JPEG or PNG, up to 50 MB). YouTube allows custom thumbnails only on verified channels; a refused thumbnail is a warning, not a failed upload.

bash
curl --fail-with-body -X POST "$ADELI_URL/api/v1/posts/youtube" \
  -H "Authorization: Bearer $ADELI_API_KEY" \
  -F 'platform=youtube' \
  -F "accountId=$ACCOUNT_ID" \
  -F 'title=Heron fishing at dawn' \
  -F 'privacy_status=unlisted' \
  -F 'made_for_kids=false' \
  -F 'tags=birds,herons' \
  -F 'video=@heron.mp4;type=video/mp4' \
  -F 'thumbnail=@heron.jpg;type=image/jpeg'

Both routes return 202 once YouTube has the file. Processing continues after:

json
{
  "id": "youtube_post_00000000-0000-0000-0000-000000000000_dQw4w9WgXcQ",
  "platform": "youtube",
  "accountId": "00000000-0000-0000-0000-000000000000",
  "profileId": "00000000-0000-4000-8000-000000000001",
  "publishId": "dQw4w9WgXcQ",
  "videoId": "dQw4w9WgXcQ",
  "permalink": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "status": "ACCEPTED",
  "requestedPrivacy": "public",
  "appliedPrivacy": "private",
  "privacyRestricted": true,
  "publishAt": null,
  "thumbnail": null,
  "statusUrl": "/api/v1/posts/youtube/status?accountId=...&profileId=...&videoId=dQw4w9WgXcQ",
  "warnings": [
    { "code": "privacy_restricted", "message": "YouTube locked this upload to private: the app has not yet passed the YouTube API Services audit" }
  ]
}

Fields

publishIdstring
The YouTube video id, the same as videoId. Pass it to the status endpoint.
appliedPrivacystring
The visibility YouTube applied. privacyRestricted is true when it is private but you asked for something else.
thumbnail"set" | "not_allowed" | "failed" | null
null when no thumbnail was sent; not_allowed when the channel cannot use custom thumbnails.
warningsobject[]
Present only when non-empty: privacy_restricted, thumbnail_not_allowed, or thumbnail_failed.

GET/api/v1/posts/youtube/statusGet YouTube upload status

Query parameters

accountIduuidRequired
videoIdstringRequired
The publishId from the upload response.
profileIduuid
Defaults to your key's profile.
json
{
  "platform": "youtube",
  "accountId": "00000000-0000-0000-0000-000000000000",
  "profileId": "00000000-0000-4000-8000-000000000001",
  "videoId": "dQw4w9WgXcQ",
  "status": "published",
  "uploadStatus": "processed",
  "processingStatus": "succeeded",
  "failureReason": null,
  "privacyStatus": "private",
  "publishAt": null,
  "permalink": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}

status is processing until YouTube finishes, then published (live, scheduled, unlisted, or private — see privacyStatus) or failed, with YouTube's failureReason. published and failed are final.

Publishing to X

POST/api/v1/postsPublish to X

An X post is text, media, or both. Send thread to publish several posts, each a reply to the one before. X publishes synchronously, so the response is the finished post.

Every X post is billed

X charges per API call, and Adeli passes the charge on at X's price: $0.015 a post, or $0.20 for a post containing a link. See Usage. Until billing is set up for your account, this returns 402 x_billing_required.

Body

platform"x"Required
accountIduuidRequired
A connected X account in your key's profile.
profileIduuid
Defaults to your key's profile.
textstringRequired
Up to 280 characters, counted as X counts them: every link is 23. Send "" for a media-only post. Adeli cannot tell which accounts have X Premium, so the 280 limit applies to all.
mediaobject[]
Up to 4 images, or 1 GIF, or 1 video. Each is { content_type, base64 } with image/jpeg, image/png, image/webp, image/gif, or video/mp4, or a video as { url }, a public HTTPS MP4 or MOV.
threadobject[]
Up to 24 further posts, each { text, media } with the same rules, published in order as replies.
bash
curl --fail-with-body -X POST "$ADELI_URL/api/v1/posts" \
  -H "Authorization: Bearer $ADELI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "x",
    "accountId": "'"$ACCOUNT_ID"'",
    "text": "Herons fish at dawn. A thread:",
    "thread": [{ "text": "They stand still for minutes at a time." }]
  }'

201 when every post is live:

json
{
  "id": "x_post_00000000-0000-0000-0000-000000000000_1840000000000000000",
  "platform": "x",
  "accountId": "00000000-0000-0000-0000-000000000000",
  "profileId": "00000000-0000-4000-8000-000000000001",
  "status": "published",
  "providerId": "1840000000000000000",
  "permalink": "https://x.com/adeli/status/1840000000000000000",
  "posts": [
    { "index": 0, "status": "published", "providerId": "1840000000000000000", "url": "https://x.com/adeli/status/1840000000000000000" },
    { "index": 1, "status": "published", "providerId": "1840000000000000001", "url": "https://x.com/adeli/status/1840000000000000001" }
  ]
}

X cannot publish a thread atomically. When a post after the first fails, the posts before it are already live and stay live: the response is 207 with status: "partial", the failed post's error, and not_attempted for the rest. Publish the remainder yourself as replies to the last providerId that went live. A failure on the first post publishes nothing and is an ordinary error.

json
{
  "status": "partial",
  "posts": [
    { "index": 0, "status": "published", "providerId": "1840000000000000000", "url": "https://x.com/adeli/status/1840000000000000000" },
    { "index": 1, "status": "failed", "error": { "code": "duplicate_post", "message": "X refused a duplicate of a recent post" } },
    { "index": 2, "status": "not_attempted" }
  ]
}

DELETE/api/v1/posts/x/{postId}Delete an X post

Deletes a post from the X account. postId is the post's providerId. It cannot be undone, and X bills the delete.

Query parameters

accountIduuidRequired
The X account the post belongs to.
profileIduuid
Defaults to your key's profile.
json
{ "id": "x_post_00000000-0000-0000-0000-000000000000_1840000000000000000", "platform": "x", "accountId": "00000000-0000-0000-0000-000000000000", "providerId": "1840000000000000000", "deleted": true }

Media limits

Instagram image8 MiB
Decoded, per image. JPEG, PNG, and WebP in; normalized to JPEG.
Instagram carousel2–10 images
Facebook photo4 MiB
Decoded, per photo — lower than Instagram's limit. Meta rejects anything larger.
Facebook multi-photo2–10 photos
Facebook video (JSON)64 MiB
Decoded from base64, MP4 only. Use multipart for MOV or WebM.
TikTok video (JSON)64 MiB
Decoded from base64, MP4 only.
TikTok video (multipart or URL)1 GB
MP4, MOV, or WebM; 3 seconds up to the creator's maximum duration. Remote URLs must be public HTTPS, and are fetched with private-network, redirect, timeout, and size protections.
TikTok photo20 MB
Per photo, 1–35 per post, normalized to JPEG at 1080×1920. Remote photo URLs are not accepted.
YouTube video (URL)1 GB
MP4, MOV, or WebM from a public HTTPS URL.
YouTube video (multipart)4 GiB
MP4, MOV, or WebM, sent to POST /api/v1/posts/youtube.
YouTube thumbnail50 MB
JPEG or PNG; custom thumbnails need a verified channel.
X image5 MB
JPEG, PNG, or WebP, up to 4 per post. The file's bytes must match its content_type.
X GIF15 MB
One per post, alone.
X video512 MB
MP4 as base64 (within the 96 MiB body limit), or MP4 or MOV from a public HTTPS URL. One per post, alone.
JSON request body96 MiB
Beyond this the request is rejected with 413 before parsing.
Multipart request4 GiB
Total across all parts.

Errors

Beyond the shared codes in Errors:

unsupported_feature422
You sent scheduled_date, add_to_queue, or async_upload. Scheduling is deferred.
invalid_post_settings422
The privacy, comment, duet, or stitch setting is not available to this creator right now, or branded content was requested without public visibility. Re-query creator info.
media_url_unverified422
TikTok has not verified this deployment's media URL prefix, which every TikTok post needs.
privacy_level_unsupported422
A TikTok video asked for a privacy level other than PUBLIC_TO_EVERYONE. Publish it publicly, or send it as a MEDIA_UPLOAD draft.
rate_limited429
TikTok is throttling the account. Retry later.
page_not_selected409
The Facebook connection exists but no Page has been chosen yet.
missing_permission403
The connection was authorized without a permission this action needs. Reconnect and approve it.
quota_exhausted429
YouTube's daily API quota, shared by every Adeli customer, is used up. The message says when it resets: midnight Pacific.
upload_limit_exceeded422
The YouTube channel has reached YouTube's own upload limit. Try again later.
provider_rejected422
YouTube refused the video's metadata, such as an unknown category_id.
x_billing_required402
X is not enabled for your account. See Usage.
duplicate_post409
X refused the post because the account published the same text recently.
invalid_media422
An X media item is not valid base64, does not match its content_type, is over its size limit, or X could not process it.
temporarily_unavailable503
X is unavailable to Adeli for a while. Retry later.
unsupported_media_type415
Content-Type must be application/json or multipart/form-data.