Quickstart
Six steps: create a key, pick a profile for your customer, connect their Instagram account to it, and publish an image. Everything here runs against a real account — there is no sandbox mode, so use an account you do not mind posting to.
You will need an Instagram professional (Business or Creator) account. TikTok and YouTube follow the same shape; the differences are called out in the Posts reference.
1. Create an API key
Sign in at /sign-in with Google. Any Google account works, and your
first sign-in creates your account, an organization you own, and a default
profile in it.
Open Settings → API keys in the dashboard sidebar, or go straight to
/settings/api-keys. Give the key a label such as
Local testing and select Create key.
The secret is shown once
Copy it immediately — Adeli stores only a hash and cannot show it to you again. A key belongs to your organization and can act on every profile in it, and deleting a key revokes it instantly.
Save it alongside the base URL:
export ADELI_URL=https://app.tryadeli.com
export ADELI_API_KEY='rk_live_...'2. List your profiles, or create one
A profile is one of your customers. Every request names the profile it acts on,
with a profileId or with an accountId that belongs to it, and nothing
defaults to one. This call lists your organization's profiles, the default
first, and confirms the key works.
Changed on 2026-10-08
Keys used to be bound to one profile, and requests without a profileId used
it. Keys now belong to the organization, and a request that names no profile
returns 400 profile_required. See Authentication.
curl --fail-with-body "$ADELI_URL/api/v1/profiles" \
-H "Authorization: Bearer $ADELI_API_KEY"{
"profiles": [
{
"id": "00000000-0000-4000-8000-000000000001",
"name": "Client A",
"externalId": "customer-123",
"metadata": {},
"isDefault": true,
"createdAt": "2026-09-08T20:00:00.000Z",
"updatedAt": "2026-09-08T20:00:00.000Z"
}
]
}To give a new customer their own profile instead, create one. The optional
Idempotency-Key makes a retry return the same profile rather than a
409 profile_name_conflict:
curl --fail-with-body -X POST "$ADELI_URL/api/v1/profiles" \
-H "Authorization: Bearer $ADELI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: customer-123" \
-d '{"name":"Client A","externalId":"customer-123"}'Save the profile's id — the next three calls need it.
export PROFILE_ID=00000000-0000-4000-8000-0000000000013. Start a connection
This returns an authUrl. Open it in your customer's browser — they
authorize Instagram directly with Meta, and Adeli never sees their credentials.
curl --fail-with-body -X POST \
"$ADELI_URL/api/v1/profiles/$PROFILE_ID/connect" \
-H "Authorization: Bearer $ADELI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"platform":"instagram","redirectUrl":"https://client.example/callback"}'{
"id": "00000000-0000-4000-8000-000000000002",
"profileId": "00000000-0000-4000-8000-000000000001",
"platform": "instagram",
"status": "pending_authorization",
"accountId": null,
"expiresAt": "2026-09-08T20:10:00.000Z",
"completedAt": null,
"error": null,
"authUrl": "https://www.instagram.com/oauth/authorize?..."
}redirectUrl must be allowlisted
Its origin has to appear in the server's
PUBLIC_API_CONNECT_REDIRECT_ORIGINS allowlist and use HTTPS, except for
localhost and 127.0.0.1 during development. An origin that is not on the
list returns 400 invalid_redirect_uri. You can omit redirectUrl entirely
and just poll instead.
4. Poll until the connection lands
The session expires ten minutes after it is created. Poll it until status is
one of the three terminal values: connected, failed, or expired.
curl --fail-with-body \
"$ADELI_URL/api/v1/profiles/$PROFILE_ID/connect/$CONNECTION_SESSION_ID" \
-H "Authorization: Bearer $ADELI_API_KEY"If you supplied a redirectUrl, the browser comes back to it with
connectionSessionId, status, and — on success — accountId in the query
string. Treat those as a hint for your UI and poll the session for the
authoritative answer.
5. List the connected accounts
curl --fail-with-body "$ADELI_URL/api/v1/accounts?profileId=$PROFILE_ID&platform=instagram" \
-H "Authorization: Bearer $ADELI_API_KEY"{
"status": "complete",
"accounts": [
{
"id": "00000000-0000-0000-0000-000000000000",
"accountId": "00000000-0000-0000-0000-000000000000",
"profileId": "00000000-0000-4000-8000-000000000001",
"platform": "instagram",
"providerId": "17841400000000000",
"displayName": "@adeli",
"displayIdentifier": "adeli",
"connectionStatus": "connected"
}
],
"errors": []
}Use accountId — Adeli's UUID — in every later request. It belongs to exactly
one profile, so a request that passes it needs no profileId. providerId is
informational.
6. Publish an image
Images are sent as base64 and must be JPEG, PNG, or WebP, decoding to no more than 8 MiB. Adeli normalizes them to JPEG before handing them to Instagram.
# Build the request body without putting base64 on the command line.
node -e '
const fs = require("node:fs");
fs.writeFileSync("/tmp/adeli-post.json", JSON.stringify({
platform: "instagram",
accountId: process.env.ACCOUNT_ID,
caption: "Published through Adeli",
image: { contentType: "image/png", base64: fs.readFileSync("./photo.png").toString("base64") },
}));
'
curl --fail-with-body -X POST "$ADELI_URL/api/v1/posts" \
-H "Authorization: Bearer $ADELI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @/tmp/adeli-post.jsonA successful publish returns 201 and the normalized post:
{
"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": "Published through Adeli",
"media": [{ "type": "IMAGE", "url": "https://media.example/staging/key.jpg", "thumbnailUrl": "https://media.example/staging/key.jpg" }],
"permalink": null,
"engagement": { "likes": null, "comments": null },
"publishedAt": "2026-04-01T12:00:00.000Z"
}Pass images instead of image — an array of 2 to 10 — to publish a carousel.
Where to go next
- Authentication — key scoping and how to keep keys out of the browser.
- Core concepts — partial responses, and the limits on every read.
- Posts — TikTok video, photo, and carousel publishing, and YouTube uploads.
- Errors — every code the API can return.