Messaging & Social
The WhatsApp, Instagram, email and calendar surface — an index of every route, and where each one is explained in depth.
Atribu is an OAuth provider for messaging as well as an attribution engine. A sibling application that needs to send a WhatsApp template, reply to an Instagram comment or read a conversation thread does it here, against the merchant's own connection, instead of registering its own Meta App.
That surface is large — WhatsApp registration, templates, broadcasts, flows, calling and health; Instagram media, triggers and conversations; email; calendar; the unified inbox — and it is documented in depth in the Node SDK reference, which carries the payload shapes, the send semantics and the webhook verification. This page is the route index and the map into it.
Where to read what
| you want to | read |
|---|---|
| send a message, reply to a comment, verify a webhook | Node SDK |
| know which events fan out and how to subscribe | Webhooks |
| attribute a WhatsApp conversation to the ad that caused it | Traffic sources and Conversion Sync |
| understand the scopes these routes need | Authentication |
Two rules that catch every new consumer
A messaging scope is not an analytics scope
mcp:read maps to analytics:read + campaigns:read and reaches no
messaging route. Messaging requires its own scopes, granted per connection, and
a send is additionally gated by an oauth_app_authorizations row for that
connection — an app that holds the scope but not the authorization is refused.
Instagram: the login id is not the user id
Webhooks deliver the IG user_id; the content APIs address the id. They are different numbers for the same account, and using one where the other belongs fails as an empty result rather than an error.
Retrying a send safely — Idempotency-Key
A send that answers with a transport error, a 502/504 or a timeout may or may
not have reached Meta. Retrying it blind can deliver the message twice. Send an
Idempotency-Key header and the retry is safe:
curl -X POST https://api.atribu.app/api/v1/messages \
-H "Authorization: Bearer atb_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8f2b1c04-3a9e-4a51-9e0d-2c7b6f10a3d5" \
-d '{"connection_id":"...","channel":"whatsapp","to":"56911112222","content":{"type":"text","text":"Hola"}}'| Where | POST /api/v1/messages (WhatsApp, Instagram and email), POST /api/v1/conversations/{id}/messages, POST /api/v1/whatsapp/conversations/{id}/messages |
| Window | 24 hours from the first request. After that the key is free again and the same value starts a new send. |
| Scope | Your API key + the route + the key value. Two partners can use the same key string without colliding, and a key claimed on one route is never replayed on another. |
| Format | 1–256 characters, any value you generate. Use something unique per logical message — Vitrina uses its own message id. |
What each repeat does.
- Same key, first request finished, inside 24 h — the original response is
replayed verbatim (same status, same
provider_message_id, samesent_at) and nothing is sent. - Same key, first request still in flight —
409 idempotency_key_in_flight. The first attempt may still deliver, so the retry is refused rather than risked. Retry the same key with backoff; once the original finishes, that key replays its response for the rest of the window. - Same key, different body —
409 idempotency_key_conflict. Reusing one key for two different messages is a caller bug, and replaying the first message's response would silently swallow the second. Not retryable as sent: resend the original body under that key, or mint a fresh key for the new message. Everything that decides what the customer receives is compared — the channel, the connection, the recipient and the content. - No header at all — every request sends. The header is opt-in and changes nothing for a consumer that does not use it.
Only a success is remembered
A non-2xx response releases the key, so a send that genuinely failed can be
retried under the same value. The flip side: if Atribu answered an error
after Meta had already accepted the message, that retry will send again.
Retry on errors that provably never reached Meta (connection refused, a
timeout before headers, a 503 from Atribu's own auth layer) and let the
Idempotency-Key cover the rest.
The Node SDK generates a key for every mutating POST
automatically. Pass your own — the one you will reuse on a retry — as
idempotencyKey:
await client.messages.send(input, { idempotencyKey: message.id });
await client.conversations.sendMessage(id, { text }, { idempotencyKey: message.id });Organic and DM attribution
/api/v1/organic/* and /api/v1/dm-ads* are the attribution reads for content
that is not a paid click: organic posts and stories with their attributed
outcomes, and the DM-ads cohort. GET /api/v1/profile/dark-messaging-spend
estimates the revenue at risk from Instagram DM conversations that carry no
attribution at all.
| Method | Path | What it does |
|---|---|---|
GET | /api/v1/calendar/calendars | List Atribu booking calendars |
POST | /api/v1/calendar/calendars | Create an Atribu booking calendar |
GET | /api/v1/calendar/calendars/{calendarId}/acl | List shares on an Atribu booking calendar |
POST | /api/v1/calendar/calendars/{calendarId}/acl | Share an Atribu booking calendar |
DELETE | /api/v1/calendar/calendars/{calendarId}/acl/{ruleId} | Revoke a share on an Atribu booking calendar |
POST | /api/v1/calendar/events | Create a calendar event |
PATCH | /api/v1/calendar/events/{id} | Update a calendar event |
DELETE | /api/v1/calendar/events/{id} | Delete (cancel) a calendar event |
GET | /api/v1/calendar/read/calendars | List external calendars (read-only) |
GET | /api/v1/calendar/read/events | List external calendar events (read-only) |
POST | /api/v1/comments/{comment_id}/private-reply | Send a DM to the user who left an IG comment |
POST | /api/v1/comments/{comment_id}/reply | Post a public reply on an IG comment thread |
GET | /api/v1/conversations | List the unified inbox's conversations |
POST | /api/v1/conversations/{id}/draft | Save a reply as a Gmail draft instead of sending it |
GET | /api/v1/conversations/{id}/labels | List an email conversation's Gmail labels |
POST | /api/v1/conversations/{id}/labels | Add/remove Gmail labels on an email conversation's thread |
POST | /api/v1/conversations/{id}/link-customer | Link an Instagram conversation to a customer (human-driven DM→payment link) |
GET | /api/v1/conversations/{id}/messages | Get a conversation's message thread |
POST | /api/v1/conversations/{id}/messages | Send a reply on a conversation |
GET | /api/v1/conversations/{id}/organic-matches | Get organic posts + ads related to a conversation |
GET | /api/v1/conversations/{id}/resolve-user | Resolve an Instagram conversation's IGSID to @username/name/photo |
GET | /api/v1/conversations/{id}/review-proposals | List a conversation's pending contact-extraction review proposals |
POST | /api/v1/conversations/{id}/review-proposals | Approve or reject a pending contact-extraction review proposal |
POST | /api/v1/conversations/{id}/unlink-customer | Undo a prior manual DM→payment link |
GET | /api/v1/dm-ads | Messaging-cohort ad performance with conversation intent |
GET | /api/v1/dm-ads/board | The DM Ads board — ranking, KPIs, funnel and creative patterns |
POST | /api/v1/email/drafts | Create a draft (reply or new) |
GET | /api/v1/email/labels | List a mailbox's labels |
POST | /api/v1/email/search | Search threads in a connected mailbox |
GET | /api/v1/email/threads/{id} | Get a normalized email thread |
POST | /api/v1/email/threads/{id}/labels | Add/remove labels on a thread |
GET | /api/v1/inbox/pending-reviews-count | Count the profile's pending review-queue proposals |
GET | /api/v1/instagram/accounts | List connected Instagram accounts |
DELETE | /api/v1/instagram/accounts/{account_id} | Disconnect an Instagram account |
GET | /api/v1/instagram/accounts/{account_id}/comment-triggers | List an account's comment-to-DM triggers |
POST | /api/v1/instagram/accounts/{account_id}/comment-triggers | Create a comment-to-DM trigger |
PATCH | /api/v1/instagram/accounts/{account_id}/comment-triggers/{trigger_id} | Update a comment-to-DM trigger |
DELETE | /api/v1/instagram/accounts/{account_id}/comment-triggers/{trigger_id} | Delete a comment-to-DM trigger |
GET | /api/v1/instagram/accounts/{account_id}/media | Page the account's synced posts |
POST | /api/v1/instagram/accounts/{account_id}/resume-comment-to-dm | Clear a tripped comment-to-DM breaker |
PATCH | /api/v1/instagram/bio-media/{media_id} | Show or hide one post on the hosted bio page |
GET | /api/v1/instagram/competitor-sources | List tracked competitor handles and hashtags |
POST | /api/v1/instagram/competitor-sources | Track a competitor handle or hashtag |
PATCH | /api/v1/instagram/competitor-sources/{id} | Enable, disable or relabel a competitor source |
DELETE | /api/v1/instagram/competitor-sources/{id} | Stop tracking a competitor source |
GET | /api/v1/instagram/competitor-sources/{id}/media | Latest captured posts for one competitor source |
POST | /api/v1/instagram/competitor-sources/refresh | Queue a competitor-research sync now |
GET | /api/v1/instagram/contacts/{igsid} | Resolve an Instagram contact's profile |
GET | /api/v1/instagram/conversations | List the connected Instagram account's conversations |
GET | /api/v1/instagram/conversations/{conversation_id}/messages | Read the messages in an Instagram conversation |
GET | /api/v1/instagram/ctm-health | Click-to-Message attribution health |
GET | /api/v1/instagram/ctm-inventory | MESSAGES-objective ads and their ManyChat mapping |
GET | /api/v1/instagram/follower-attribution | Which ads produced followers |
GET | /api/v1/instagram/identity-reviews | Pending DM identity-match reviews |
POST | /api/v1/instagram/identity-reviews/{review_id}/decide | Approve or reject a DM identity match |
GET | /api/v1/instagram/media | List the connected Instagram account's media |
POST | /api/v1/instagram/media | Create an Instagram media container (publish step 1) |
GET | /api/v1/instagram/media/{media_id} | Read one Instagram media object |
POST | /api/v1/instagram/media/publish | Publish an Instagram media container (publish step 2) |
GET | /api/v1/instagram/search-conversations | Typeahead search over Instagram conversations |
GET | /api/v1/instagram/short-links | List Instagram short links |
POST | /api/v1/instagram/short-links | Mint an Instagram short link |
POST | /api/v1/instagram/short-links/follow-proxy | Mint a Follow Proxy link for an Instagram handle |
POST | /api/v1/instagram/sync | Queue an Instagram sync now |
GET | /api/v1/instagram/triggers | List comment-to-DM triggers |
POST | /api/v1/instagram/triggers | Create a comment-to-DM trigger |
PATCH | /api/v1/instagram/triggers/{id} | Update a comment-to-DM trigger |
DELETE | /api/v1/instagram/triggers/{id} | Delete a comment-to-DM trigger |
POST | /api/v1/instagram/triggers/{id}/test-dm | Send the trigger's opening_message as a DM to a test IGSID |
POST | /api/v1/instagram/triggers/resume | Manually clear a tripped comment-to-DM circuit |
GET | /api/v1/instagram/unlinked-payments | List recent cash payments with no linked Instagram conversation |
POST | /api/v1/messages | Send a channel-agnostic message |
POST | /api/v1/messages/typing | Mark a WhatsApp message read (with or without a typing indicator) |
GET | /api/v1/organic/posts | List organic posts with attribution |
GET | /api/v1/organic/posts/{id} | Get one organic post's attribution detail |
GET | /api/v1/organic/posts/{id}/intelligence | Get one organic post's AI analysis and twin ads |
GET | /api/v1/organic/stories/{id} | Get one Instagram story's attribution detail |
GET | /api/v1/whatsapp/account-health | Get WhatsApp channel health |
GET | /api/v1/whatsapp/automatic-events | Read the Automatic Events opt-in state for this profile's WABAs |
PATCH | /api/v1/whatsapp/automatic-events | Record the Automatic Events opt-in confirmation |
GET | /api/v1/whatsapp/broadcasts | List broadcasts |
POST | /api/v1/whatsapp/broadcasts | Create a broadcast |
GET | /api/v1/whatsapp/broadcasts/{id} | Get a broadcast + recipients |
PATCH | /api/v1/whatsapp/broadcasts/{id} | Cancel a broadcast |
POST | /api/v1/whatsapp/broadcasts/{id}/send | Start sending a broadcast |
GET | /api/v1/whatsapp/calling | Read a number's calling settings |
POST | /api/v1/whatsapp/calling | Enable or update a number's calling settings |
GET | /api/v1/whatsapp/calling/sip-credentials | Read a number's SIP digest credentials |
GET | /api/v1/whatsapp/conversations/{id}/messages | List messages in a WhatsApp conversation |
POST | /api/v1/whatsapp/conversations/{id}/messages | Send a text or media message into a WhatsApp conversation |
POST | /api/v1/whatsapp/conversations/{id}/messages/declared-source-prompt | Ask a WhatsApp customer "how did you find us?" |
POST | /api/v1/whatsapp/conversations/{id}/messages/interactive | Send an interactive button message into a WhatsApp conversation |
POST | /api/v1/whatsapp/conversations/{id}/messages/template | Send a template message into a WhatsApp conversation |
GET | /api/v1/whatsapp/declared-source-prompt | Read the "how did you find us?" prompt opt-in and the spec a bot follows |
PATCH | /api/v1/whatsapp/declared-source-prompt | Turn the "how did you find us?" prompt on or off |
GET | /api/v1/whatsapp/flows | List WhatsApp flows |
POST | /api/v1/whatsapp/flows | Create a WhatsApp flow |
GET | /api/v1/whatsapp/flows/{flowId} | Read a WhatsApp flow |
POST | /api/v1/whatsapp/flows/{flowId} | Update a WhatsApp flow's metadata |
DELETE | /api/v1/whatsapp/flows/{flowId} | Delete a WhatsApp flow (DRAFT only) |
GET | /api/v1/whatsapp/flows/{flowId}/assets | List a WhatsApp flow's assets |
POST | /api/v1/whatsapp/flows/{flowId}/assets | Upload a WhatsApp flow's JSON |
POST | /api/v1/whatsapp/flows/{flowId}/deprecate | Deprecate a WhatsApp flow |
POST | /api/v1/whatsapp/flows/{flowId}/publish | Publish a WhatsApp flow |
POST | /api/v1/whatsapp/label-mappings | Map a GoHighLevel pipeline stage to a WhatsApp label |
DELETE | /api/v1/whatsapp/label-mappings/{id} | Remove a pipeline-stage → WhatsApp-label mapping |
GET | /api/v1/whatsapp/labels | Read this profile's WhatsApp labels and pipeline-stage mappings |
POST | /api/v1/whatsapp/labels | Create a WhatsApp label on this profile's WABA |
POST | /api/v1/whatsapp/media | Upload WhatsApp media (pre-upload → media_id) |
GET | /api/v1/whatsapp/media/{mediaId} | Resolve WhatsApp media (media_id → hosted URL) |
GET | /api/v1/whatsapp/otp-capture | Read back a published OTP capture |
POST | /api/v1/whatsapp/otp-capture | Publish an OTP captured off Meta's verification call |
GET | /api/v1/whatsapp/registration/funding | Read a WABA's funding + status (payment gate) |
GET | /api/v1/whatsapp/registration/phone-numbers | List the phone numbers on a WABA |
POST | /api/v1/whatsapp/registration/phone-numbers | Add (or migrate) a phone number onto a WABA |
POST | /api/v1/whatsapp/registration/register | Register the number + subscribe Atribu's app |
POST | /api/v1/whatsapp/registration/request-code | Request the registration OTP (voice) |
POST | /api/v1/whatsapp/registration/subscribe | Subscribe Atribu's app to an already-registered WABA |
GET | /api/v1/whatsapp/registration/subscribed-apps | List apps subscribed to a WABA |
POST | /api/v1/whatsapp/registration/verify-code | Verify the registration OTP |
GET | /api/v1/whatsapp/templates | List WhatsApp message templates |
POST | /api/v1/whatsapp/templates | Create a WhatsApp message template |
DELETE | /api/v1/whatsapp/templates/{name} | Delete a WhatsApp message template by name |
POST | /api/v1/whatsapp/templates/sync | Sync WhatsApp message templates from Meta |
Generated from openapi.json. The full request and response schema for every operation is in the OpenAPI document.