Atribu
API Reference

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 toread
send a message, reply to a comment, verify a webhookNode SDK
know which events fan out and how to subscribeWebhooks
attribute a WhatsApp conversation to the ad that caused itTraffic sources and Conversion Sync
understand the scopes these routes needAuthentication

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"}}'
WherePOST /api/v1/messages (WhatsApp, Instagram and email), POST /api/v1/conversations/{id}/messages, POST /api/v1/whatsapp/conversations/{id}/messages
Window24 hours from the first request. After that the key is free again and the same value starts a new send.
ScopeYour 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.
Format1–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, same sent_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.

MethodPathWhat it does
GET/api/v1/calendar/calendarsList Atribu booking calendars
POST/api/v1/calendar/calendarsCreate an Atribu booking calendar
GET/api/v1/calendar/calendars/{calendarId}/aclList shares on an Atribu booking calendar
POST/api/v1/calendar/calendars/{calendarId}/aclShare an Atribu booking calendar
DELETE/api/v1/calendar/calendars/{calendarId}/acl/{ruleId}Revoke a share on an Atribu booking calendar
POST/api/v1/calendar/eventsCreate 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/calendarsList external calendars (read-only)
GET/api/v1/calendar/read/eventsList external calendar events (read-only)
POST/api/v1/comments/{comment_id}/private-replySend a DM to the user who left an IG comment
POST/api/v1/comments/{comment_id}/replyPost a public reply on an IG comment thread
GET/api/v1/conversationsList the unified inbox's conversations
POST/api/v1/conversations/{id}/draftSave a reply as a Gmail draft instead of sending it
GET/api/v1/conversations/{id}/labelsList an email conversation's Gmail labels
POST/api/v1/conversations/{id}/labelsAdd/remove Gmail labels on an email conversation's thread
POST/api/v1/conversations/{id}/link-customerLink an Instagram conversation to a customer (human-driven DM→payment link)
GET/api/v1/conversations/{id}/messagesGet a conversation's message thread
POST/api/v1/conversations/{id}/messagesSend a reply on a conversation
GET/api/v1/conversations/{id}/organic-matchesGet organic posts + ads related to a conversation
GET/api/v1/conversations/{id}/resolve-userResolve an Instagram conversation's IGSID to @username/name/photo
GET/api/v1/conversations/{id}/review-proposalsList a conversation's pending contact-extraction review proposals
POST/api/v1/conversations/{id}/review-proposalsApprove or reject a pending contact-extraction review proposal
POST/api/v1/conversations/{id}/unlink-customerUndo a prior manual DM→payment link
GET/api/v1/dm-adsMessaging-cohort ad performance with conversation intent
GET/api/v1/dm-ads/boardThe DM Ads board — ranking, KPIs, funnel and creative patterns
POST/api/v1/email/draftsCreate a draft (reply or new)
GET/api/v1/email/labelsList a mailbox's labels
POST/api/v1/email/searchSearch threads in a connected mailbox
GET/api/v1/email/threads/{id}Get a normalized email thread
POST/api/v1/email/threads/{id}/labelsAdd/remove labels on a thread
GET/api/v1/inbox/pending-reviews-countCount the profile's pending review-queue proposals
GET/api/v1/instagram/accountsList connected Instagram accounts
DELETE/api/v1/instagram/accounts/{account_id}Disconnect an Instagram account
GET/api/v1/instagram/accounts/{account_id}/comment-triggersList an account's comment-to-DM triggers
POST/api/v1/instagram/accounts/{account_id}/comment-triggersCreate 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}/mediaPage the account's synced posts
POST/api/v1/instagram/accounts/{account_id}/resume-comment-to-dmClear 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-sourcesList tracked competitor handles and hashtags
POST/api/v1/instagram/competitor-sourcesTrack 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}/mediaLatest captured posts for one competitor source
POST/api/v1/instagram/competitor-sources/refreshQueue a competitor-research sync now
GET/api/v1/instagram/contacts/{igsid}Resolve an Instagram contact's profile
GET/api/v1/instagram/conversationsList the connected Instagram account's conversations
GET/api/v1/instagram/conversations/{conversation_id}/messagesRead the messages in an Instagram conversation
GET/api/v1/instagram/ctm-healthClick-to-Message attribution health
GET/api/v1/instagram/ctm-inventoryMESSAGES-objective ads and their ManyChat mapping
GET/api/v1/instagram/follower-attributionWhich ads produced followers
GET/api/v1/instagram/identity-reviewsPending DM identity-match reviews
POST/api/v1/instagram/identity-reviews/{review_id}/decideApprove or reject a DM identity match
GET/api/v1/instagram/mediaList the connected Instagram account's media
POST/api/v1/instagram/mediaCreate an Instagram media container (publish step 1)
GET/api/v1/instagram/media/{media_id}Read one Instagram media object
POST/api/v1/instagram/media/publishPublish an Instagram media container (publish step 2)
GET/api/v1/instagram/search-conversationsTypeahead search over Instagram conversations
GET/api/v1/instagram/short-linksList Instagram short links
POST/api/v1/instagram/short-linksMint an Instagram short link
POST/api/v1/instagram/short-links/follow-proxyMint a Follow Proxy link for an Instagram handle
POST/api/v1/instagram/syncQueue an Instagram sync now
GET/api/v1/instagram/triggersList comment-to-DM triggers
POST/api/v1/instagram/triggersCreate 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-dmSend the trigger's opening_message as a DM to a test IGSID
POST/api/v1/instagram/triggers/resumeManually clear a tripped comment-to-DM circuit
GET/api/v1/instagram/unlinked-paymentsList recent cash payments with no linked Instagram conversation
POST/api/v1/messagesSend a channel-agnostic message
POST/api/v1/messages/typingMark a WhatsApp message read (with or without a typing indicator)
GET/api/v1/organic/postsList organic posts with attribution
GET/api/v1/organic/posts/{id}Get one organic post's attribution detail
GET/api/v1/organic/posts/{id}/intelligenceGet 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-healthGet WhatsApp channel health
GET/api/v1/whatsapp/automatic-eventsRead the Automatic Events opt-in state for this profile's WABAs
PATCH/api/v1/whatsapp/automatic-eventsRecord the Automatic Events opt-in confirmation
GET/api/v1/whatsapp/broadcastsList broadcasts
POST/api/v1/whatsapp/broadcastsCreate 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}/sendStart sending a broadcast
GET/api/v1/whatsapp/callingRead a number's calling settings
POST/api/v1/whatsapp/callingEnable or update a number's calling settings
GET/api/v1/whatsapp/calling/sip-credentialsRead a number's SIP digest credentials
GET/api/v1/whatsapp/conversations/{id}/messagesList messages in a WhatsApp conversation
POST/api/v1/whatsapp/conversations/{id}/messagesSend a text or media message into a WhatsApp conversation
POST/api/v1/whatsapp/conversations/{id}/messages/declared-source-promptAsk a WhatsApp customer "how did you find us?"
POST/api/v1/whatsapp/conversations/{id}/messages/interactiveSend an interactive button message into a WhatsApp conversation
POST/api/v1/whatsapp/conversations/{id}/messages/templateSend a template message into a WhatsApp conversation
GET/api/v1/whatsapp/declared-source-promptRead the "how did you find us?" prompt opt-in and the spec a bot follows
PATCH/api/v1/whatsapp/declared-source-promptTurn the "how did you find us?" prompt on or off
GET/api/v1/whatsapp/flowsList WhatsApp flows
POST/api/v1/whatsapp/flowsCreate 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}/assetsList a WhatsApp flow's assets
POST/api/v1/whatsapp/flows/{flowId}/assetsUpload a WhatsApp flow's JSON
POST/api/v1/whatsapp/flows/{flowId}/deprecateDeprecate a WhatsApp flow
POST/api/v1/whatsapp/flows/{flowId}/publishPublish a WhatsApp flow
POST/api/v1/whatsapp/label-mappingsMap 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/labelsRead this profile's WhatsApp labels and pipeline-stage mappings
POST/api/v1/whatsapp/labelsCreate a WhatsApp label on this profile's WABA
POST/api/v1/whatsapp/mediaUpload WhatsApp media (pre-upload → media_id)
GET/api/v1/whatsapp/media/{mediaId}Resolve WhatsApp media (media_id → hosted URL)
GET/api/v1/whatsapp/otp-captureRead back a published OTP capture
POST/api/v1/whatsapp/otp-capturePublish an OTP captured off Meta's verification call
GET/api/v1/whatsapp/registration/fundingRead a WABA's funding + status (payment gate)
GET/api/v1/whatsapp/registration/phone-numbersList the phone numbers on a WABA
POST/api/v1/whatsapp/registration/phone-numbersAdd (or migrate) a phone number onto a WABA
POST/api/v1/whatsapp/registration/registerRegister the number + subscribe Atribu's app
POST/api/v1/whatsapp/registration/request-codeRequest the registration OTP (voice)
POST/api/v1/whatsapp/registration/subscribeSubscribe Atribu's app to an already-registered WABA
GET/api/v1/whatsapp/registration/subscribed-appsList apps subscribed to a WABA
POST/api/v1/whatsapp/registration/verify-codeVerify the registration OTP
GET/api/v1/whatsapp/templatesList WhatsApp message templates
POST/api/v1/whatsapp/templatesCreate a WhatsApp message template
DELETE/api/v1/whatsapp/templates/{name}Delete a WhatsApp message template by name
POST/api/v1/whatsapp/templates/syncSync WhatsApp message templates from Meta

Generated from openapi.json. The full request and response schema for every operation is in the OpenAPI document.

Next steps

On this page