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:

bash
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"
json
{
  "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:

bash
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.

bash
export PROFILE_ID=00000000-0000-4000-8000-000000000001

3. 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"}'
json
{
  "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"
json
{
  "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.json

A successful publish returns 201 and the normalized post:

json
{
  "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.