/v1/instagram/dm

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.

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

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

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

EndpointCostInstagram call?What it does
POST /v1/instagram/dm/sessionsfreeclaims an accountClaim a sending identity. Not idempotent — a second call claims a second account.
GET /v1/instagram/dm/sessions$0.0005noYour live identities, newest first. Use this to reconcile instead of re-opening.
POST /v1/instagram/dm/sessions/{session_id}/messages$0.02yesMessage a recipient as that identity. Creates the conversation on first contact.
GET /v1/instagram/dm/conversations$0.0005noEvery thread you have started, across all your identities, recent activity first.
GET /v1/instagram/dm/conversations/{conversation_id}$0.0005noOne thread with its full message history, both directions.
POST /v1/instagram/dm/conversations/{conversation_id}/reply$0.02yesFollow up on the thread's own identity — one send, no recipient lookup.
POST /v1/instagram/dm/conversations/{conversation_id}/refresh$0.001yesPoll 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:

StateMeaningWhat to do
sentDelivered.Nothing.
operation_in_progressDispatched, still settling.Re-read the conversation shortly.
delivery_unknownDispatched 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.

HTTPerror.codeMeaning
503no_serveable_personaNo sending capacity is free right now. Retry shortly.
404not_foundThat session or conversation is not yours. We never confirm it exists elsewhere.
503upstream_unavailableThe 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.