Instagram DM outreach
Send Instagram DMs from a managed account pool, hold the conversation, and read replies — without ever supplying an Instagram login. This is the only part of the API that writes to the outside world, so it behaves differently from every read endpoint. Read this before you integrate.
A session is an identity
Opening an outreach session claims a managed Instagram account and holds it for that session's life. Every message you send under that session goes out as the same account, so the recipient sees one consistent sender.
Open a second session and you get a different account. That is how the same creator can be messaged twice, independently — a second campaign, or a second attempt on a dead thread. You never see which account carries a thread; you address sessions, conversations, and recipients.
Hold one session per campaign and send many recipients under it. Do not open a session per message. Each one holds a real account from a finite pool, and there is currently no way to hand it back — see what you cannot do yet.
The four calls
Note the shape: sends address a session, reads address a conversation. The conversation endpoints take no session id at all — we resolve one from your account, which is also why a conversation belonging to someone else simply reads as not found.
1 Claim a sending identity.
curl -X POST "https://api.virev.ai/v1/instagram/dm/sessions" \
-H "Authorization: Bearer $VIREV_API_KEY"
# { "data": { "session_id": "es_9tQf4bXyzAbC3n" }, "meta": { ... } }2 Message a recipient as that identity.
curl -X POST "https://api.virev.ai/v1/instagram/dm/sessions/es_9tQf4bXyzAbC3n/messages" \
-H "Authorization: Bearer $VIREV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "creator_handle",
"text": "hey! loved your latest reel 👋",
"client_token": "campaign-42-creator_handle"
}'3 Pull replies and follow up. to accepts an @handle, a profile URL, or a numeric user id.
# Check one thread for new inbound (a live Instagram read — on demand, not on a loop)
curl -X POST "https://api.virev.ai/v1/instagram/dm/conversations/1/refresh" \
-H "Authorization: Bearer $VIREV_API_KEY"
# Follow up on the thread's own identity
curl -X POST "https://api.virev.ai/v1/instagram/dm/conversations/1/reply" \
-H "Authorization: Bearer $VIREV_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "text": "following up — still interested?", "client_token": "campaign-42-followup-1" }'Operations
Seven operations. Full schemas and response examples are in the endpoint reference.
| Endpoint | Cost | Instagram call? | What it does |
|---|---|---|---|
| POST /v1/instagram/dm/sessions | free | claims an account | Claim a sending identity. Not idempotent — a second call claims a second account. |
| GET /v1/instagram/dm/sessions | $0.0005 | no | Your live identities, newest first. Use this to reconcile instead of re-opening. |
| POST /v1/instagram/dm/sessions/{session_id}/messages | $0.02 | yes | Message a recipient as that identity. Creates the conversation on first contact. |
| GET /v1/instagram/dm/conversations | $0.0005 | no | Every thread you have started, across all your identities, recent activity first. |
| GET /v1/instagram/dm/conversations/{conversation_id} | $0.0005 | no | One thread with its full message history, both directions. |
| POST /v1/instagram/dm/conversations/{conversation_id}/reply | $0.02 | yes | Follow up on the thread's own identity — one send, no recipient lookup. |
| POST /v1/instagram/dm/conversations/{conversation_id}/refresh | $0.001 | yes | Poll the thread inbox, including the pending message-request tray, for new replies. |
Retry safety — read this before you ship
client_token makes a send at-most-once within one conversation: retrying with the same token
on the same thread returns the original outcome instead of delivering a second message.
Optional in the schema, required in practice. If you omit client_token, the server mints a fresh one for that call — so at-most-once does not span your own HTTP retries, and two retries become two real DMs to a real person. Generate a stable
token per logical message and reuse it.
The token is scoped to a conversation, not to your account. Deduplication looks up (conversation, client_token). A different recipient is a different conversation, so
replaying the same token against a new recipient sends another real DM — it is not rejected and it is not deduplicated. Never treat a token as a
global "send once" key; scope your own bookkeeping per recipient.
A deduplicated retry is free. When a replay returns sent.deduplicated: true, nothing new was delivered and the call is billed at $0.00 — so retrying a send whose connection dropped can never charge you twice for one message.
Sends that complete return a delivery state. A dispatch that fails ambiguously returns an error rather than a state — the ambiguous outcome is recorded, and you observe it by retrying with the same token or by reading the thread:
| State | Meaning | What to do |
|---|---|---|
sent | Delivered. | Nothing. |
operation_in_progress | Dispatched, still settling. | Re-read the conversation shortly. |
delivery_unknown | Dispatched but unconfirmed. | Do not resend under a new token. Reuse the same token, or read the thread to see what landed. |
Reusing a token on the same thread with a different body is a
conflict, not idempotency — the send is refused rather than silently returning the earlier message. (It
currently surfaces as a 502 upstream_error rather than a 409; treat any non-success on a
token replay as "do not invent a new token".) One token identifies one logical message on one thread.
Errors specific to outreach
These sit alongside the standard API errors. All three are raised before any deduction — you are never charged for capacity you did not get.
| HTTP | error.code | Meaning |
|---|---|---|
| 503 | no_serveable_persona | No sending capacity is free right now. Retry shortly. |
| 404 | not_found | That session or conversation is not yours. We never confirm it exists elsewhere. |
| 503 | upstream_unavailable | The DM service could not be reached. Retry; nothing was sent. |
What you cannot do yet
There is no public way to close a session. Once opened, it holds a sending identity indefinitely — sessions do not expire on their own, and releasing one back to the pool is currently an internal operation. Threads on a released session stay readable but can no longer be sent on, because that identity may since have been assigned elsewhere.
Plan around it: open deliberately, reuse aggressively, and when you lose track of your ids, reconcile with GET /v1/instagram/dm/sessions rather than opening another.
This is cold outreach, not your inbox
This API sends from our managed account pool to recipients who have no prior relationship with you. It is not connected to your own Instagram account, it does not read your own Instagram inbox, and it does not require you to connect an account or grant Instagram permissions.
If what you want is to manage replies inside an inbox you already own, through your own connected account, that is a different product with a different identity and permission model. This API is not it.
Try it without an API key
The DM playground in the portal drives this same engine — sign in and it works with no API key and no balance. Treat it as a feel-for-the-product surface rather than the API contract: it is a shared demo where every visitor works in one common conversation space, sends are rate-limited per connection, and each new outreach claims a fresh identity instead of reusing one.