{"openapi":"3.1.0","info":{"title":"Sonar API","description":"Send WhatsApp messages and templates from your own system, check delivery and keep contacts in step. The guide, with webhooks, signatures and examples, is at [/developers](https://sonar.whats-hub.co.za/developers).\n\n**Authentication:** every call needs `Authorization: Bearer YOUR_API_KEY`. Each WhatsApp number has its own key, under WhatsApp numbers > Advanced (for developers) in Sonar. It sends from that number: keep it on your server, never in a browser or app.\n\n**Errors** are JSON: `{\"detail\": \"what went wrong\"}`. **Times** are UTC. Sonar doesn't throttle calls; WhatsApp allows about 1 message every 6 seconds to the same person.\n\nThe API comes with every plan, the free trial too.","version":"1.0.0"},"servers":[{"url":"https://sonar.whats-hub.co.za"}],"paths":{"/contacts/upsert":{"post":{"tags":["Contacts"],"summary":"Add or update a contact","description":"Matched on the phone number: a new number is added, an existing contact updated. `tags` are added and `remove_tags` taken off (a list or one comma-separated string). `custom` sets your own contact fields by key. `opt_in: true` records marketing consent with `opt_in_source`; `opted_out: true` unsubscribes them.","operationId":"add_or_update_a_contact","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactUpsertRequest"},"example":{"phone":"+27 82 123 4567","name":"Sam","tags":["Hold placed"],"custom":{"arrival_date":"2026-11-14"},"opt_in":true,"opt_in_source":"booking form"}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResult"}}}},"400":{"description":"The request can't be done; `detail` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one Sonar doesn't know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The body doesn't match the schema; `detail` lists each problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"bearer":[]}]}},"/send":{"post":{"tags":["Messages"],"summary":"Send a message","operationId":"send_a_message","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageRequest"},"example":{"recipient":"27821234567","message":"Your order is ready for collection."}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResult"}}}},"400":{"description":"The recipient isn't valid, or WhatsApp refused the message, for example because they haven't messaged you in the last 24 hours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one Sonar doesn't know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The body doesn't match the schema; `detail` lists each problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"description":"A free-text message to someone who has messaged this number in the last 24 hours. Outside that window, send an approved template instead.","security":[{"bearer":[]}]}},"/message-status":{"post":{"tags":["Messages"],"summary":"Check delivery","description":"The latest status of up to 100 messages sent from this number: `sent`, `delivered`, `read` or `failed`, by `message_id` or by the `reference`s you gave. Times are UTC.","operationId":"check_delivery","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageStatusRequest"},"example":{"wamids":["wamid.HBgL..."],"references":["booking-881"]}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusResult"}}}},"401":{"description":"No API key, or one Sonar doesn't know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The body doesn't match the schema; `detail` lists each problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"bearer":[]}]}},"/send-template":{"post":{"tags":["Messages"],"summary":"Send a template","operationId":"send_a_template","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendTemplateRequest"},"example":{"recipient":"27821234567","template_name":"booking_confirmed","language_code":"en_US","body_parameters":["Sam","12 May"],"buttons":[{"index":0,"kind":"url","payload":"P-881"}],"reference":"booking-881"}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResult"}}}},"400":{"description":"The template isn't approved, its fill-ins don't match, or WhatsApp refused it. If WhatsApp didn't answer in time, `detail` says the message may have been sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one Sonar doesn't know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Past your plan's included messages and your balance can't cover this one, or the free trial's messages are used up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"A marketing template to someone who opted out of marketing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A message with this `reference` is still being sent, or an earlier one with it may have gone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The body doesn't match the schema; `detail` lists each problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"description":"An approved template, at any time. `body_parameters` fill `{{1}}`, `{{2}}` and so on in order. `buttons` give the values for buttons that need one when you send: a link button's ending (`kind: url`), a coupon (`copy_code`) or a quick reply. Give your own `reference` and a repeat with it from this number within 24 hours sends nothing: you get the first answer with `duplicate: true`. The `message_id` comes straight back; delivery follows on the `message.status` webhook.","security":[{"bearer":[]}]}},"/media/{media_id}":{"get":{"tags":["Files"],"summary":"Download a file","description":"A file someone sent you: the `media.url` in a `message.received` webhook update. Any of the business's number keys works.","operationId":"download_a_file","parameters":[{"name":"media_id","in":"path","required":true,"schema":{"type":"string","title":"Media Id"}}],"responses":{"200":{"description":"The file, with its content type.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"No API key, or one Sonar doesn't know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The body doesn't match the schema; `detail` lists each problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"bearer":[]}]}},"/create-template":{"post":{"tags":["Templates"],"summary":"Create a template","description":"A template on this number's WhatsApp account. WhatsApp reviews it, usually within minutes and at most 24 hours, and can approve a utility template as marketing, so check `category` in the answer. `body_text` uses `{{1}}`, `{{2}}` with a sample for each in `body_example`; WhatsApp rejects text that starts or ends with a fill-in. A link button ending in `{{1}}` takes a sample ending in `example`. Templates with an image, video or document at the top are made in Sonar.","operationId":"create_a_template","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateCreate"},"example":{"name":"booking_confirmed","category":"UTILITY","language_code":"en_US","body_text":"Hi {{1}}, your booking for {{2}} is confirmed.","body_example":["Sam","12 May"],"buttons":[{"type":"URL","text":"View booking","url":"https://example.com/b/{{1}}","example":"P-881"}]}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateCreated"}}}},"400":{"description":"The request can't be done; `detail` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one Sonar doesn't know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The body doesn't match the schema; `detail` lists each problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"bearer":[]}]}},"/list-templates":{"get":{"tags":["Templates"],"summary":"List templates","description":"The templates on this number's WhatsApp account, with their approval status and category, straight from WhatsApp.","operationId":"list_templates","responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateList"}}}},"400":{"description":"The request can't be done; `detail` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one Sonar doesn't know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"WhatsApp couldn't be reached; try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The body doesn't match the schema; `detail` lists each problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"bearer":[]}]}}},"components":{"schemas":{"ContactUpsertRequest":{"properties":{"phone":{"type":"string","title":"Phone","description":"Their number in international format, or their WhatsApp user ID to update a contact Sonar has."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"tags":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"remove_tags":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Remove Tags"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"opt_in":{"type":"boolean","title":"Opt In","default":false},"opt_in_source":{"type":"string","title":"Opt In Source","default":"api","description":"Where the consent came from, kept with the consent record."},"opted_out":{"type":"boolean","title":"Opted Out","default":false},"custom":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Custom"}},"type":"object","required":["phone"],"title":"ContactUpsertRequest"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"MessageRequest":{"properties":{"recipient":{"type":"string","title":"Recipient","description":"Their number in international format, like 27821234567 or +27 82 123 4567, or their WhatsApp user ID."},"message":{"type":"string","title":"Message","description":"The text."},"preview_url":{"type":"boolean","title":"Preview Url","default":false,"description":"Show a preview of the first link."}},"type":"object","required":["recipient","message"],"title":"MessageRequest"},"MessageStatusRequest":{"properties":{"wamids":{"items":{"type":"string"},"type":"array","title":"Wamids","default":[],"description":"Message IDs from this number, up to 100."},"references":{"items":{"type":"string"},"type":"array","title":"References","default":[],"description":"References you gave /send-template, up to 100."}},"type":"object","title":"MessageStatusRequest","description":"Delivery status lookup for messages this API key's number sent: by wamid, or by the\nreference given to /send-template."},"SendTemplateRequest":{"properties":{"recipient":{"type":"string","title":"Recipient","description":"Their number in international format, or their WhatsApp user ID."},"template_name":{"type":"string","title":"Template Name","description":"An approved template on this number's WhatsApp account."},"language_code":{"type":"string","title":"Language Code","default":"en_US","description":"The template's language, like en_US."},"body_parameters":{"items":{"type":"string"},"type":"array","title":"Body Parameters","default":[],"description":"Values for {{1}}, {{2}} and so on, in order. One line each."},"buttons":{"items":{"$ref":"#/components/schemas/TemplateButton"},"type":"array","title":"Buttons","default":[],"description":"Values for buttons that need one when you send."},"header_document":{"anyOf":[{"$ref":"#/components/schemas/TemplateHeaderDocument"},{"type":"null"}],"description":"A PDF for a template with a document at the top."},"header_image":{"anyOf":[{"$ref":"#/components/schemas/TemplateHeaderImage"},{"type":"null"}],"description":"An image for a template with an image at the top."},"reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reference","description":"Your own id for this message, up to 100 characters. Stops a retry sending it twice."}},"type":"object","required":["recipient","template_name"],"title":"SendTemplateRequest"},"TemplateButton":{"properties":{"index":{"type":"integer","title":"Index","description":"The button's position among the template's buttons, from 0."},"payload":{"type":"string","title":"Payload","default":"","description":"The value."},"kind":{"type":"string","title":"Kind","default":"quick_reply","description":"url, copy_code or quick_reply."}},"type":"object","required":["index"],"title":"TemplateButton"},"TemplateCreate":{"properties":{"name":{"type":"string","title":"Name","description":"Lowercase letters, numbers and underscores."},"category":{"type":"string","title":"Category","default":"MARKETING","description":"UTILITY, MARKETING or AUTHENTICATION."},"language_code":{"type":"string","title":"Language Code","default":"en_US"},"header_format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Header Format","description":"TEXT for a text header."},"header_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Header Text","description":"Up to 60 characters."},"header_example":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Header Example","description":"A sample for a {{1}} in the header."},"body_text":{"type":"string","title":"Body Text","default":"","description":"The message, with {{1}}, {{2}} fill-ins."},"body_example":{"items":{"type":"string"},"type":"array","title":"Body Example","default":[],"description":"A sample value for each fill-in."},"footer_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Footer Text","description":"Up to 60 characters."},"buttons":{"items":{"$ref":"#/components/schemas/TemplateCreateButton"},"type":"array","title":"Buttons","default":[]},"ttl_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ttl Seconds","description":"How long WhatsApp keeps trying to deliver it."},"add_security_recommendation":{"type":"boolean","title":"Add Security Recommendation","default":false,"description":"AUTHENTICATION only: adds WhatsApp's do-not-share line."},"code_expiration_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Code Expiration Minutes","description":"AUTHENTICATION only: the code's expiry shown in the footer."}},"type":"object","required":["name"],"title":"TemplateCreate"},"TemplateCreateButton":{"properties":{"type":{"type":"string","title":"Type","description":"URL, QUICK_REPLY, PHONE_NUMBER or OTP."},"text":{"type":"string","title":"Text","default":"","description":"The button's label."},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"URL buttons: the link, which may end in {{1}}."},"phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone Number","description":"PHONE_NUMBER buttons: in international format."},"example":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Example","description":"A link ending in {{1}}: a sample ending."},"otp_type":{"type":"string","title":"Otp Type","default":"COPY_CODE","description":"OTP buttons: COPY_CODE, ONE_TAP or ZERO_TAP."}},"type":"object","required":["type"],"title":"TemplateCreateButton"},"TemplateHeaderDocument":{"properties":{"filename":{"type":"string","title":"Filename","default":"document.pdf"},"base64":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Base64"},"link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Link"}},"type":"object","title":"TemplateHeaderDocument","description":"PDF for a template whose header format is DOCUMENT. Give the bytes as base64 (uploaded\nto Meta's media endpoint and attached by id, so nothing has to be publicly hosted) or a\npublic https link. `filename` is what the recipient sees on the attachment."},"TemplateHeaderImage":{"properties":{"link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Link"},"base64":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Base64"},"mime":{"type":"string","title":"Mime","default":"image/jpeg"}},"type":"object","title":"TemplateHeaderImage","description":"Image for a template whose header format is IMAGE: a public https link, or the bytes\nas base64 (uploaded to Meta's media endpoint and attached by id). JPEG or PNG."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"ContactResult":{"properties":{"id":{"title":"Id","type":"integer"},"wa_id":{"title":"Wa Id","type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Name"},"created":{"title":"Created","type":"boolean"},"opted_out":{"title":"Opted Out","type":"boolean"},"tags":{"items":{"type":"string"},"title":"Tags","type":"array"},"custom":{"title":"Custom","type":"object"},"opt_in_at":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Opt In At"}},"required":["id","wa_id","created","opted_out","tags","custom"],"title":"ContactResult","type":"object"},"SendResult":{"properties":{"message_id":{"title":"Message Id","type":"string"},"status":{"title":"Status","type":"string"},"reference":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Reference"},"duplicate":{"anyOf":[{"type":"boolean"},{"type":"null"}],"default":null,"title":"Duplicate"}},"required":["message_id","status"],"title":"SendResult","type":"object"},"DeliveryStatus":{"properties":{"status":{"title":"Status","type":"string"},"error_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Error Code"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Error Message"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Updated At"},"reference":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Reference"}},"required":["status"],"title":"DeliveryStatus","type":"object"},"StatusResult":{"properties":{"statuses":{"additionalProperties":{"$ref":"#/components/schemas/DeliveryStatus"},"title":"Statuses","type":"object"}},"required":["statuses"],"title":"StatusResult","type":"object"},"TemplateCreated":{"properties":{"id":{"title":"Id","type":"string"},"status":{"title":"Status","type":"string"},"category":{"title":"Category","type":"string"},"name":{"title":"Name","type":"string"}},"required":["id","status","category","name"],"title":"TemplateCreated","type":"object"},"TemplateItem":{"properties":{"name":{"title":"Name","type":"string"},"status":{"title":"Status","type":"string"},"category":{"title":"Category","type":"string"},"language":{"title":"Language","type":"string"}},"required":["name","status","category","language"],"title":"TemplateItem","type":"object"},"TemplateList":{"properties":{"templates":{"items":{"$ref":"#/components/schemas/TemplateItem"},"title":"Templates","type":"array"}},"required":["templates"],"title":"TemplateList","type":"object"},"Error":{"properties":{"detail":{"title":"Detail","type":"string"}},"required":["detail"],"title":"Error","type":"object"}},"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"A number's API key (public API) or your partner key (partner API)."}}},"tags":[{"name":"Messages","description":"Send messages and templates, and check delivery."},{"name":"Templates","description":"Create and list WhatsApp templates."},{"name":"Contacts","description":"Keep Sonar's contacts in step with your system."},{"name":"Files","description":"Files people send you."}]}