Managing accounts
Account management is part of the Enterprise plan and needs the accounts permission.
POST Create an account
/accounts · needs accounts · Enterprise plan
Creates an account. channel is required — linkedin (created, then signed in), andtelegram (created, then signed in by QR code or phone number) can be set up this way; an account's capabilities.createAndAuthenticate says whether its channel can. The Idempotency-Key header is required, so a retry can't create two. On any other plan this is a 403 plan_required.
curl -X POST https://messengeragent.dev/api/v1/accounts \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: onboard-acme-sales" \
-H "Content-Type: application/json" \
-d '{"channel": "linkedin", "name": "Acme sales"}'HTTP/1.1 201 Created
{
"id": "01a115b1-…",
"channel": "linkedin",
"name": "Acme sales",
"identifier": null,
"status": "disconnected",
"canSend": false,
"capabilities": { "createAndAuthenticate": true, "asyncSend": true, … },
"createdAt": "2026-10-07T09:20:11+00:00"
}POST Adopt a signed-in account
/accounts · with remoteAccountId · needs accounts · Enterprise plan · LinkedIn
If the person has already signed in to LinkedIn somewhere else, don't make them do it twice. Add remoteAccountId — the id of their account at linkedin-api — to Create an account. Instead of creating a new account that has to be signed in, it takes over the one that exists: there is no sign-in and no credentials are involved. The answer is the usual account (201), already connected if their session is live. An account's capabilities.adoptExisting says whether its channel supports this.
- Same linkedin-api tenant. The account must be visible to the linkedin-api key this service uses — the one your own linkedin-api calls use too. An id from any other tenant is indistinguishable from an id that doesn't exist: a tenant mismatch shows up as
404 remote_account_not_found. - One account, one owner. An id that is already linked here — to you or to any other customer — is
409 remote_account_already_linked, so no one can adopt an account that isn't theirs. An account you delete stays reserved: adopt it again only under a new id. - Status. What linkedin-api says becomes the account's status:
logged_in→connected,session_expired→needs_attention,logged_out→disconnected. When a session ends the account becomesneeds_attention, and it returns toconnectedby itself once the person signs in again at linkedin-api — you call nothing here. - Webhook. Adopting registers this service's webhook on the account at linkedin-api, as for any account. That replaces any webhook your tenant had registered on that account — don't register your own on accounts you hand over.
- Sign-out and delete work as for any account. Delete also deletes the account at linkedin-api, so an adopted account is gone there too; if it's already gone, the delete still succeeds.
- Errors. A blank, overlong (over 64 characters) or malformed id — only letters, digits,
-and_— is422 invalid_request. If linkedin-api can't be reached it is503 channel_unavailable: retry with the sameIdempotency-Key. A retry that already succeeded replays the first answer and never makes a second account.
# The person already signed in at linkedin-api, which gave you their account id. Attach it — no sign-in here.
curl -X POST https://messengeragent.dev/api/v1/accounts \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: adopt-ada-1" \
-H "Content-Type: application/json" \
-d '{"channel": "linkedin", "name": "Ada", "remoteAccountId": "7d2f5c1e9a"}'
# → 201, the usual account: "status": "connected", "canSend": true, "capabilities": { "adoptExisting": true, … }POST Sign in an account
/accounts/{id}/authentication · needs accounts · Enterprise plan
Signs the account in with the account owner's own login — for LinkedIn, their email and password. The answer is an authentication: an attempt that may pause for a code or an approval before it ends. It expires, and a new attempt is a new one.
in_progress— a browser is signing in. Poll.awaiting_code— LinkedIn sent a verification code. Submit it.awaiting_approval— waiting for the owner to approve on their phone. Keep polling.succeeded— the account is signed in; itsstatusis nowconnected.failed— it didn't work.reasonandmessagesay why (a wrong password, a CAPTCHA that needs the owner to sign in on their own device first, a timeout).
# 1. Start. This is slow — a real browser signs in, up to about a minute. Allow ~90 s.
curl -X POST https://messengeragent.dev/api/v1/accounts/ACCOUNT_ID/authentication \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: signin-acme-1" \
-H "Content-Type: application/json" \
-d '{"email": "owner@acme.com", "password": "••••••••"}'
# → { "id": "9f3c…", "status": "awaiting_code", "message": "LinkedIn sent a verification code. Submit it to continue." }
# 2. If it is awaiting_code, answer it.
curl -X POST https://messengeragent.dev/api/v1/accounts/ACCOUNT_ID/authentication/9f3c…/code \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{"code": "123456"}'
# → 202 { "id": "9f3c…", "status": "in_progress" }
# 3. Poll until it settles.
curl https://messengeragent.dev/api/v1/accounts/ACCOUNT_ID/authentication/9f3c… -H "Authorization: Bearer $API_KEY"
# → { "id": "9f3c…", "status": "succeeded" }The password is forwarded and forgotten: it is never stored, logged, written to the activity log, or used to match a retry. Starting a sign-in needs an Idempotency-Key; if your request times out, retry with the same key and you get the same authentication back rather than a second sign-in. A sign-in that failed frees its key, so start again with it. Each step is in your activity log.
POST Sign in a Telegram account
/accounts/{id}/authentication · needs accounts · Enterprise plan
The same authentication, with Telegram's own steps. Create the account first ("channel": "telegram"), then start a sign-in either way: "method": "qr" returns a code to scan in Telegram, and "method": "phone" with phoneNumber has Telegram send a login code. Either may then ask for the account's two-step verification password. Starting again abandons the sign-in under way, so it is also the way back from a number you got wrong.
awaiting_scan—qrCodeis adata:image/pngaddress to show. It is replaced every half minute or so; keep polling and show the newest.awaiting_code— Telegram sent a login code to the account.POST …/authentication/{id}/code.awaiting_password— the account has two-step verification.POST …/authentication/{id}/passwordwith{"password": "…"}.succeeded/failed— as above. A wrong code, password or number is a422 invalid_credentialswith Telegram's own words, and the sign-in stays on the same step; a flood wait is a429withRetry-After.
# 1a. By QR code. The answer carries the code to scan, as a data: address you can put in an <img>.
curl -X POST https://messengeragent.dev/api/v1/accounts/ACCOUNT_ID/authentication \
-H "Authorization: Bearer $API_KEY" -H "Idempotency-Key: tg-signin-1" -H "Content-Type: application/json" \
-d '{"method": "qr"}'
# → { "id": "…", "status": "awaiting_scan", "qrCode": "data:image/png;base64,…", "message": "Scan the QR code in Telegram: Settings → Devices → Link Desktop Device. …" }
# 1b. Or by phone number: Telegram sends a login code to the account.
curl -X POST https://messengeragent.dev/api/v1/accounts/ACCOUNT_ID/authentication \
-H "Authorization: Bearer $API_KEY" -H "Idempotency-Key: tg-signin-2" -H "Content-Type: application/json" \
-d '{"method": "phone", "phoneNumber": "+1 555 123 4567"}'
# → { "id": "…", "status": "awaiting_code" }
# 2. Answer the code, and — if the account has two-step verification — its password.
curl -X POST https://messengeragent.dev/api/v1/accounts/ACCOUNT_ID/authentication/AUTH_ID/code -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" -d '{"code": "12345"}'
# → { "status": "awaiting_password" } (or "succeeded")
curl -X POST https://messengeragent.dev/api/v1/accounts/ACCOUNT_ID/authentication/AUTH_ID/password -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" -d '{"password": "••••••••"}'
# 3. Poll until it settles. While awaiting_scan the qrCode is replaced every half minute or so: show the newest.
curl https://messengeragent.dev/api/v1/accounts/ACCOUNT_ID/authentication/AUTH_ID -H "Authorization: Bearer $API_KEY"
# → { "status": "succeeded" }The phone number, code and password are forwarded and forgotten: never stored, logged, written to the activity log, or used to match a retry. An authentication here has one id, the account's own, because an account has one sign-in at a time. Sign-out and deletion work as for any account; deleting removes the account at Telegram's gateway first, and does nothing if that can't be done.
POST Sign out an account
/accounts/{id}/sign-out · needs accounts · Enterprise plan
Ends the channel's session. The account, its conversations and its contacts stay, and it can be signed in again later. The answer is the account, now disconnected. Repeating it is harmless, so no Idempotency-Key is needed. If the channel can't be reached you get an error, never "signed out" — the session may still be live.
DELETE Delete an account
/accounts/{id} · needs accounts · Enterprise plan
Removes the account and ends its session at the channel. It answers 204 with no body. If the channel can't be reached, nothing is deleted and you get a 503 to retry, so a 204 can be relied on: no session is left behind. Deleting an account that is already gone is a 404, so a retry after a timeout is safe to treat as success. What the account did stays in your activity log.
# Sign out: the account stays, disconnected
curl -X POST https://messengeragent.dev/api/v1/accounts/ACCOUNT_ID/sign-out -H "Authorization: Bearer $API_KEY"
# Delete: 204, or a 503 if the channel can't be reached (nothing was deleted — retry)
curl -X DELETE https://messengeragent.dev/api/v1/accounts/ACCOUNT_ID -H "Authorization: Bearer $API_KEY"