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.
{
"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"RequiredaccountIduuidRequired- 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 withimages. imagesobject[]- A carousel of 2 to 10 images, each shaped like
image. Mutually exclusive withimage. 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 }.xandyare 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:
{
"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"RequiredaccountIduuidRequired- A connected Facebook Page in your key's profile.
messagestring- Up to 2200 characters. The caption shown with the post.
imageobject- One photo —
contentTypeandbase64. 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:
{
"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
accountIduuidRequiredpublishIdstringRequired- 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.
{
"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"RequiredaccountIduuidRequired- A connected TikTok account.
profileIduuid- Defaults to your key's profile.
post_mode"DIRECT_POST" | "MEDIA_UPLOAD"- Defaults to
DIRECT_POST, which publishes.MEDIA_UPLOADsends 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_EVERYONEor omitted. Photos: one ofPUBLIC_TO_EVERYONE,MUTUAL_FOLLOW_FRIENDS,FOLLOWER_OF_CREATOR,SELF_ONLYthat the creator currently allows; defaults toPUBLIC_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 withphotos. photosobject[]- 1 to 35 photos, each
{ content_type, base64 }. Remote photo URLs are not accepted. Mutually exclusive withvideo.
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:
{
"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:
{
"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.
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'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:
{
"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
accountIduuidRequiredpublishIdstringRequired- From the publish response. 1–200 characters of [A-Za-z0-9._~-].
profileIduuid- Defaults to your key's profile.
{
"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
statusstringPROCESSING_DOWNLOADwhile TikTok fetches the media; thenPUBLISH_COMPLETE,SEND_TO_USER_INBOX(a draft was delivered), orFAILED. 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 asframe_rate_check_failed,video_pull_failed, orspam_risk_too_many_posts(the daily limit). publiclyAvailableboolean | null- Whether the post is publicly viewable.
falseafter TikTok reports it is no longer public — moderation, or the creator made it private.nullwhen 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"RequiredaccountIduuidRequired- 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 }withvideo/mp4,video/quicktime, orvideo/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.
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.
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:
{
"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.
privacyRestrictedis true when it isprivatebut you asked for something else. thumbnail"set" | "not_allowed" | "failed" | nullnullwhen no thumbnail was sent;not_allowedwhen the channel cannot use custom thumbnails.warningsobject[]- Present only when non-empty:
privacy_restricted,thumbnail_not_allowed, orthumbnail_failed.
GET/api/v1/posts/youtube/statusGet YouTube upload status
Query parameters
accountIduuidRequiredvideoIdstringRequired- The publishId from the upload response.
profileIduuid- Defaults to your key's profile.
{
"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"RequiredaccountIduuidRequired- 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 }withimage/jpeg,image/png,image/webp,image/gif, orvideo/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.
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:
{
"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.
{
"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.
{ "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 imagesFacebook photo4 MiB- Decoded, per photo — lower than Instagram's limit. Meta rejects anything larger.
Facebook multi-photo2–10 photosFacebook 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, orasync_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/jsonormultipart/form-data.