{"info":{"name":"Buddy API","description":"Set base_url and api_key in a private Postman environment. Never export a populated secret.","schema":"https://schema.getpostman.com/json/collection/v2.1.0/collection.json"},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{api_key}}","type":"string"}]},"variable":[{"key":"base_url","value":"https://usebuddy.app/api/v1"},{"key":"api_key","value":""},{"key":"id","value":"YOUR_ID","description":"Replace with a resource ID returned by Buddy."},{"key":"event_id","value":"YOUR_EVENT_ID","description":"Replace with a resource ID returned by Buddy."},{"key":"delivery_id","value":"YOUR_DELIVERY_ID","description":"Replace with a resource ID returned by Buddy."},{"key":"operation_id","value":"","description":"Set a unique value for each write; reuse only for retries."}],"item":[{"name":"Record a purchase and match a campaign click","request":{"method":"POST","description":"Record an order confirmed by your checkout backend. Capture buddy_click_id from the destination URL after a Buddy campaign click, preserve it with the checkout, and submit it with the checkout customer phone or matching Buddy subscriber ID. The campaign is resolved from the click when omitted. Attribution requires the exact message recipient and an order within seven days after the recorded click. Orders without verified click evidence remain unattributed. This matches activity and does not independently verify payment or causation. Test keys store isolated unattributed purchases; live click IDs are rejected in test mode. Repeated source plus external_ref deduplicates within workspace and mode.","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."},{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/conversions","body":{"mode":"raw","raw":"{\n  \"amount_minor\": 4200,\n  \"currency\": \"GBP\",\n  \"external_ref\": \"order-1042\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Record a cumulative purchase refund","request":{"method":"POST","description":"Submit the total refunded amount for an existing source and external_ref. Repeated or older cumulative totals cannot reduce a previously recorded refund. The refund cannot exceed the original amount. Test keys only update test purchases.","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."},{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/conversions/refund","body":{"mode":"raw","raw":"{\n  \"external_ref\": \"order-1042\",\n  \"refunded_minor\": 1200\n}","options":{"raw":{"language":"json"}}}}},{"name":"Create an embedded component session","request":{"method":"POST","description":"Creates a short-lived, origin-bound component capability. Keep the parent API key on your server. The component can only read its configured resource.","header":[{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/embedded_sessions","body":{"mode":"raw","raw":"{\n  \"component\": \"template_picker\",\n  \"origin\": \"https://your-app.example\",\n  \"expires_in\": 300\n}","options":{"raw":{"language":"json"}}}}},{"name":"Revoke an embedded session","request":{"method":"DELETE","description":"Revokes a component session created by this credential.","header":[],"url":"{{base_url}}/embedded_sessions/{{id}}"}},{"name":"Inspect an embedded session","request":{"method":"GET","description":"Returns session metadata without its client secret.","header":[],"url":"{{base_url}}/embedded_sessions/{{id}}"}},{"name":"List campaigns","request":{"method":"GET","header":[],"url":"{{base_url}}/campaigns"}},{"name":"Get a campaign and its results","request":{"method":"GET","header":[],"url":"{{base_url}}/campaigns/{{id}}"}},{"name":"Create a draft campaign","request":{"method":"POST","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."},{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/campaigns","body":{"mode":"raw","raw":"{\n  \"title\": \"Welcome\",\n  \"template\": \"buddy_test_hello\",\n  \"variables\": [\n    \"there\"\n  ],\n  \"audience\": {\n    \"all\": true\n  }\n}","options":{"raw":{"language":"json"}}}}},{"name":"Queue a campaign now","request":{"method":"POST","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."},{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/campaigns/{{id}}/send","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}}},{"name":"Schedule a campaign","request":{"method":"POST","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."},{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/campaigns/{{id}}/schedule","body":{"mode":"raw","raw":"{\n  \"send_at\": \"2099-01-01T00:00:00.000Z\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Cancel a scheduled campaign","request":{"method":"POST","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."},{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/campaigns/{{id}}/cancel","body":{"mode":"raw","raw":"{}","options":{"raw":{"language":"json"}}}}},{"name":"List campaign recipients","request":{"method":"GET","header":[],"url":"{{base_url}}/campaigns/{{id}}/recipients"}},{"name":"Send a verification code","request":{"method":"POST","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."},{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/otp","body":{"mode":"raw","raw":"{\n  \"to\": \"+15550000009\",\n  \"template\": \"buddy_test_otp\",\n  \"expires_in\": 300\n}","options":{"raw":{"language":"json"}}}}},{"name":"Verify a single-use code","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/otp/verify","body":{"mode":"raw","raw":"{\n  \"id\": \"otp_00000000000000000000000000000001\",\n  \"code\": \"123456\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Check your key","request":{"method":"GET","description":"The workspace, key, mode, scopes, plan, rate limits, WhatsApp connection and usage for the key you call with. Any valid key can call it.","header":[],"url":"{{base_url}}/me"}},{"name":"Record events","request":{"method":"POST","description":"Record one event or a batch of up to 100. Producer IDs deduplicate for 30 days within the workspace and mode. Historical events never start automations. Test-mode events never run live automations. An unresolved external ID is stored without granting consent or a messaging recipient.","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."},{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/events","body":{"mode":"raw","raw":"{\n  \"id\": \"order-1042\",\n  \"name\": \"order.paid\",\n  \"external_id\": \"customer-1042\",\n  \"properties\": {\n    \"total\": 42\n  }\n}","options":{"raw":{"language":"json"}}}}},{"name":"List recorded events","request":{"method":"GET","header":[],"url":"{{base_url}}/events"}},{"name":"List subscribers","request":{"method":"GET","description":"Newest first. Filter by status, tag, phone, external ID or creation time.","header":[],"url":"{{base_url}}/subscribers"}},{"name":"Add a subscriber","request":{"method":"POST","description":"Adds a person who agreed to hear from you. If the number is already a subscriber, its tags and fields are merged and you get 200. Someone who opted out is never re-subscribed: you get 409 subscriber_opted_out, and only they can opt back in.","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."},{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/subscribers","body":{"mode":"raw","raw":"{\n  \"phone\": \"+44 7700 900123\",\n  \"name\": \"Amira\",\n  \"tags\": [\n    \"early-list\"\n  ],\n  \"external_id\": \"cus_1042\",\n  \"consent\": {\n    \"source\": \"checkout_form\",\n    \"text\": \"Send me order updates on WhatsApp\",\n    \"collected_at\": \"2026-10-15T09:30:00Z\"\n  }\n}","options":{"raw":{"language":"json"}}}}},{"name":"Get a subscriber","request":{"method":"GET","description":"Look up by ID, by phone (phone:+447700900123) or by your external ID (ext:cus_1042).","header":[],"url":"{{base_url}}/subscribers/{{id}}"}},{"name":"Update a subscriber","request":{"method":"PATCH","description":"Change the name, external ID or attributes. Attributes are merged; set one to null to remove it. Consent can't be changed here: use opt_out, and only the person can opt back in.","header":[{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/subscribers/{{id}}","body":{"mode":"raw","raw":"{\n  \"name\": \"Amira K\",\n  \"attributes\": {\n    \"orders\": 3,\n    \"city\": \"Lyon\",\n    \"old_field\": null\n  }\n}","options":{"raw":{"language":"json"}}}}},{"name":"Erase a subscriber","request":{"method":"DELETE","description":"Deletes the subscriber's personal data and consent history (for erasure requests under GDPR). If they had opted out, a one-way hash of their number is kept so they can't be added again by mistake.","header":[],"url":"{{base_url}}/subscribers/{{id}}"}},{"name":"Add or remove tags","request":{"method":"POST","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."},{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/subscribers/{{id}}/tags","body":{"mode":"raw","raw":"{\n  \"add\": [\n    \"repeat-buyer\"\n  ],\n  \"remove\": [\n    \"early-list\"\n  ]\n}","options":{"raw":{"language":"json"}}}}},{"name":"Record an opt-out","request":{"method":"POST","description":"Record an opt-out collected somewhere else, such as an email unsubscribe link. It can't be undone through the API.","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."},{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/subscribers/{{id}}/opt_out","body":{"mode":"raw","raw":"{\n  \"reason\": \"unsubscribed_by_email\",\n  \"source\": \"email_footer\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Consent history","request":{"method":"GET","description":"Every opt-in and opt-out recorded for this subscriber, newest first, with the evidence stored.","header":[],"url":"{{base_url}}/subscribers/{{id}}/consent"}},{"name":"List tags","request":{"method":"GET","description":"Every tag in use, with how many subscribers have it. Most used first.","header":[],"url":"{{base_url}}/tags"}},{"name":"List templates","request":{"method":"GET","description":"Approved templates by default. Each one says which parameters a send needs: body parameters (positional or named), the header type, and which buttons take a value.","header":[],"url":"{{base_url}}/templates"}},{"name":"Get a template","request":{"method":"GET","description":"By ID or by name. Pass language when the template exists in more than one.","header":[],"url":"{{base_url}}/templates/{{id}}"}},{"name":"Check a recipient's service window","request":{"method":"GET","description":"Read-only snapshot of opt-out and the 24-hour text window. Connection, consent and template checks still run when sending.","header":[],"url":"{{base_url}}/messages/eligibility?to=%2B15550000003"}},{"name":"Send a message","request":{"method":"POST","description":"Sends a template, or free text within 24 hours of the person's last message. The message is accepted (202, status queued) and sent straight after; follow its status with GET /messages/{id} or the message.* webhook events. Needs an Idempotency-Key, so a retry never sends twice. Test keys send to the simulator: use the magic numbers +15550000001 to +15550000004 for read, failed, replied and STOP outcomes, and the buddy_test_hello and buddy_test_order_update templates before you connect a number.","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."},{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/messages","body":{"mode":"raw","raw":"{\n  \"to\": \"+33 6 12 34 56 78\",\n  \"template\": \"order_shipped\",\n  \"language\": \"en\",\n  \"variables\": [\n    \"Amira\",\n    \"1042\"\n  ],\n  \"buttons\": [\n    {\n      \"index\": 0,\n      \"url_suffix\": \"1042\"\n    }\n  ],\n  \"metadata\": {\n    \"order_id\": \"1042\"\n  }\n}","options":{"raw":{"language":"json"}}}}},{"name":"List messages","request":{"method":"GET","description":"Messages sent through the API, newest first. Filter by recipient, status, template or creation time.","header":[],"url":"{{base_url}}/messages"}},{"name":"Get a message","request":{"method":"GET","description":"The message's status, when each status happened, and why it failed if it did.","header":[],"url":"{{base_url}}/messages/{{id}}"}},{"name":"List webhook endpoints","request":{"method":"GET","description":"Newest first. A test key lists test endpoints, which receive test events only.","header":[],"url":"{{base_url}}/webhook_endpoints"}},{"name":"Add a webhook endpoint","request":{"method":"POST","description":"Registers an https URL to receive events. The response includes the signing secret; keep it on your server. Every delivery is a POST with a JSON body { id, type, version, at, livemode, data } and a Buddy-Signature header (t=<unix time>,v1=<hex HMAC-SHA256 of t + \".\" + the raw body, keyed with the signing secret>). Reply with any 2xx within 10 seconds. Failed deliveries are retried 8 more times over about 3 days.","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."},{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/webhook_endpoints","body":{"mode":"raw","raw":"{\n  \"url\": \"https://example.com/webhooks/buddy\",\n  \"events\": [\n    \"message.replied\",\n    \"subscriber.opted_out\"\n  ],\n  \"description\": \"Order system\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Get a webhook endpoint","request":{"method":"GET","header":[],"url":"{{base_url}}/webhook_endpoints/{{id}}"}},{"name":"Update a webhook endpoint","request":{"method":"PATCH","description":"Change the URL, the events or the description, or turn the endpoint off and on. Turning it back on clears the reason it was turned off.","header":[{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/webhook_endpoints/{{id}}","body":{"mode":"raw","raw":"{\n  \"events\": [\n    \"*\"\n  ],\n  \"status\": \"enabled\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"Delete a webhook endpoint","request":{"method":"DELETE","description":"Stops all deliveries to it and deletes its delivery log.","header":[],"url":"{{base_url}}/webhook_endpoints/{{id}}"}},{"name":"Rotate the signing secret","request":{"method":"POST","description":"Creates a new signing secret. For 24 hours (or expire_previous_in seconds) deliveries carry two v1= signatures, one per secret, so you can switch your server over without missing an event.","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."},{"key":"Content-Type","value":"application/json"}],"url":"{{base_url}}/webhook_endpoints/{{id}}/rotate_secret","body":{"mode":"raw","raw":"{\n  \"expire_previous_in\": 86400\n}","options":{"raw":{"language":"json"}}}}},{"name":"Reveal the signing secret","request":{"method":"GET","description":"Returns the current signing secret. Needs webhooks:write, because the secret lets its holder sign events.","header":[],"url":"{{base_url}}/webhook_endpoints/{{id}}/secret"}},{"name":"Send a test event","request":{"method":"POST","description":"Sends a ping event to this endpoint straight away, once, with no retries. Read the result from the endpoint's deliveries.","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."}],"url":"{{base_url}}/webhook_endpoints/{{id}}/test"}},{"name":"List retained webhook events","request":{"method":"GET","description":"Matching events since the endpoint was created, retained for 30 days even while disabled. Filter by since and paginate to recover missed events.","header":[],"url":"{{base_url}}/webhook_endpoints/{{id}}/events"}},{"name":"Replay a retained event","request":{"method":"POST","description":"Queues a retained event with its original ID and version. Re-enable a paused endpoint first. Receivers must deduplicate repeated event IDs.","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."}],"url":"{{base_url}}/webhook_endpoints/{{id}}/events/{{event_id}}/resend"}},{"name":"List deliveries","request":{"method":"GET","description":"Every event sent to this endpoint in the last 30 days, newest first, with the result of the last attempt.","header":[],"url":"{{base_url}}/webhook_endpoints/{{id}}/deliveries"}},{"name":"Resend a delivery","request":{"method":"POST","description":"Sends the same event (same id and body) to this endpoint again, as a new delivery with its own retries. Receivers that dedupe on the event id treat it as the same event.","header":[{"key":"Idempotency-Key","value":"{{operation_id}}","description":"Set once per operation; retain for retries."}],"url":"{{base_url}}/webhook_endpoints/{{id}}/deliveries/{{delivery_id}}/resend"}}]}