{"openapi":"3.0.3","info":{"title":"InboxWA API","version":"1.0.0","description":"The InboxWA API: the same surface the web app runs on, plus a reporting API for unread, unanswered, seen-by, receipts, replies and first-response times. Authenticate with a bearer API key."},"servers":[{"url":"https://inboxwa.online","description":"Production"}],"tags":[{"name":"Messaging","description":"Send a message to one contact, or a broadcast to many, through the WhatsApp Cloud API, and list your templates. Part of the core API (Growth and Scale). Sending needs the whatsapp_api scope and is not held to the default per-key request limit."},{"name":"Contacts","description":"Create and update contacts, their tags and custom attributes, and their consent: part of the core API (Growth and Scale). Reading contacts, marking a chat read, and deleting contacts are part of the full API (Scale plan)."},{"name":"Reporting","description":"Purpose-built reads for the accountability questions: unread, unanswered, seen-by, receipts, replies, first response and assignment history. Part of the full API (Scale plan), except Check a key."},{"name":"Team","description":"Roster, performance, SLA and the hand-off queue. Part of the full API (Scale plan). The API is the same surface the web app uses, over 500 routes; GET /api/docs/routes lists every one."},{"name":"Webhooks","description":"Signed events pushed to your endpoint, and the tools to configure and test them. Webhooks are part of Growth (five core events) and Scale (all events); Starter has none. Calling the configuration endpoints with a key is part of the full API (Scale plan); the dashboard does the same for Growth."},{"name":"API keys","description":"Create, scope, limit, rotate and audit keys. Key management needs a workspace admin: a dashboard session, or a full-access key created by an admin. Keys are part of Growth and Scale; Starter has none. Managing keys with a key is part of the full API (Scale plan)."},{"name":"Credits","description":"AI credit balance and the ledger behind it. Part of the full API (Scale plan)."}],"security":[{"bearerAuth":[]}],"paths":{"/api/reporting/v1/whoami":{"get":{"operationId":"whoami","tags":["Reporting"],"summary":"Check a key","description":"Reports which workspace the credential belongs to, its scopes, whether it is read-only, and what the plan includes: api_tier is core on Growth (the core API) and full on Scale (every call). Costs no data, so it is the first call to make when wiring an integration.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Whoami"},"example":{"workspace_id":195,"auth":"api_key","key":{"id":7,"scopes":["inbox:read","team_management:read"],"read_only":true},"api_access":true,"api_tier":"full","reporting_api":"v1"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":[],"note":"Any valid key, on Growth and Scale."},"x-errors":[],"x-plan":"growth"}},"/api/reporting/v1/conversations":{"get":{"operationId":"listConversations","tags":["Reporting"],"summary":"List conversations","description":"Chats active in the window, newest first, each with unread count, who spoke last, state, assignee and SLA deadline. Filters combine. The window defaults to the last 30 days so one call cannot scan a whole history; counts are measured inside it.","parameters":[{"name":"unread","in":"query","description":"Only chats with customer messages nobody has opened.","required":false,"schema":{"type":"boolean"}},{"name":"needs_reply","in":"query","description":"Only chats where the customer is waiting: they spoke last and no delivered reply followed. Failed sends do not count as replies.","required":false,"schema":{"type":"boolean"}},{"name":"state","in":"query","description":"Only chats in this state.","required":false,"schema":{"type":"string","enum":["AI_ONLY","HANDOFF_PENDING","HUMAN_ACTIVE"]}},{"name":"assigned_to","in":"query","description":"Only chats assigned to this team member (user id).","required":false,"schema":{"type":"integer"}},{"name":"sla_breached","in":"query","description":"Only chats waiting for a person past their SLA deadline.","required":false,"schema":{"type":"boolean"}},{"name":"q","in":"query","description":"Search contact name or phone number.","required":false,"schema":{"type":"string"}},{"name":"since","in":"query","description":"Start of the window: an ISO 8601 timestamp or a date (YYYY-MM-DD, start of that UTC day). Default: 30 days ago. Alias: from.","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-10-01"},{"name":"to","in":"query","description":"End of the window (ISO 8601 or date). Default: now. Alias: until.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"sort","in":"query","description":"last_activity: newest first. waiting_longest: customers who have waited longest first.","required":false,"schema":{"type":"string","enum":["last_activity","waiting_longest"],"default":"last_activity"}},{"name":"limit","in":"query","description":"Page size, 1 to 200. Default 50.","required":false,"schema":{"type":"integer","default":50,"minimum":1,"maximum":200}},{"name":"cursor","in":"query","description":"The next_cursor from the previous page. Treat it as opaque.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationList"},"example":{"data":[{"phone":"919876543210","contact_name":"Ravi Kumar","state":"HUMAN_ACTIVE","handover_reason":"human_claimed","assigned_to":{"user_id":41,"name":"Anita"},"claimed_by":{"user_id":41,"name":"Anita"},"unread_count":3,"needs_reply":true,"last_message":{"message_id":"wamid.HBgM...C3","sender_type":"customer","sent_at":"2026-10-02T09:06:40.000Z","preview":"Server is still down, please check"},"waiting_since":"2026-10-02T09:06:40.000Z","waiting_seconds":1260,"sla_deadline_at":null,"sla_breached":false,"labels":["vip"],"counts":{"messages":5,"from_customer":3,"from_bot":1}}],"next_cursor":"eyJrIjoidHMiLCJ0cyI6IjIwMjYtMTAtMDJUMDk6MDY6NDAuMDAwWiIsInBob25lIjoiOTE5ODc2NTQzMjEwIn0","window":{"from":"2026-09-02T00:00:00.000Z","to":"2026-10-02T09:30:00.000Z"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","contacts","inbox:read","contacts:read"],"note":"Chat content: messages, names and phone numbers."},"x-errors":[{"status":400,"error":"invalid_state","when":"state is not one of the three states."},{"status":400,"error":"invalid_assigned_to","when":"assigned_to is not a user id."},{"status":400,"error":"invalid_sort","when":"sort is not recognised."},{"status":400,"error":"invalid_since","when":"since or to is not a date."},{"status":400,"error":"window_too_large","when":"The window is longer than 400 days."}],"x-plan":"scale"}},"/api/reporting/v1/conversations/unread-summary":{"get":{"operationId":"unreadSummary","tags":["Reporting"],"summary":"Unread and waiting totals","description":"Workspace totals: how many chats have unread messages, how many customers are waiting for a reply, the longest wait, and the same split by assignee. Aggregates only; no phone numbers.","parameters":[{"name":"since","in":"query","description":"Start of the window: an ISO 8601 timestamp or a date (YYYY-MM-DD, start of that UTC day). Default: 30 days ago. Alias: from.","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-10-01"},{"name":"to","in":"query","description":"End of the window (ISO 8601 or date). Default: now. Alias: until.","required":false,"schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnreadSummary"},"example":{"window":{"from":"2026-09-02T00:00:00.000Z","to":"2026-10-02T09:30:00.000Z"},"unread_chats":12,"unread_messages":31,"oldest_unread_at":"2026-10-01T04:12:00.000Z","waiting_chats":9,"oldest_waiting_since":"2026-10-01T04:12:00.000Z","longest_wait_seconds":105480,"by_assignee":[{"assigned_to":{"user_id":41,"name":"Anita"},"unread_chats":5,"unread_messages":14,"oldest_unread_at":"2026-10-02T06:30:00.000Z","waiting_chats":4,"oldest_waiting_since":"2026-10-02T06:30:00.000Z"}],"unassigned":{"assigned_to":null,"unread_chats":7,"unread_messages":17,"oldest_unread_at":"2026-10-01T04:12:00.000Z","waiting_chats":5,"oldest_waiting_since":"2026-10-01T04:12:00.000Z"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","team_management","analytics","inbox:read","team_management:read","analytics:read"],"note":"Team accountability data: who opened, who replied, how fast."},"x-errors":[],"x-plan":"scale"}},"/api/reporting/v1/conversations/unanswered":{"get":{"operationId":"unansweredConversations","tags":["Reporting"],"summary":"Unanswered conversations","description":"Chats where the customer has been waiting longer than older_than, longest wait first. Accepts the same filters as List conversations.","parameters":[{"name":"older_than","in":"query","description":"Minimum wait: a number with a unit, such as 30m, 2h or 1d. Default 30m. Up to 90 days.","required":false,"schema":{"type":"string","default":"30m"},"example":"30m"},{"name":"state","in":"query","description":"Only chats in this state.","required":false,"schema":{"type":"string","enum":["AI_ONLY","HANDOFF_PENDING","HUMAN_ACTIVE"]}},{"name":"assigned_to","in":"query","description":"Only chats assigned to this team member.","required":false,"schema":{"type":"integer"}},{"name":"q","in":"query","description":"Search contact name or phone number.","required":false,"schema":{"type":"string"}},{"name":"since","in":"query","description":"Start of the window: an ISO 8601 timestamp or a date (YYYY-MM-DD, start of that UTC day). Default: 30 days ago. Alias: from.","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-10-01"},{"name":"to","in":"query","description":"End of the window (ISO 8601 or date). Default: now. Alias: until.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","description":"Page size, 1 to 200. Default 50.","required":false,"schema":{"type":"integer","default":50,"minimum":1,"maximum":200}},{"name":"cursor","in":"query","description":"The next_cursor from the previous page. Treat it as opaque.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnansweredList"},"example":{"data":[{"phone":"919876543210","contact_name":"Ravi Kumar","state":"HUMAN_ACTIVE","handover_reason":"human_claimed","assigned_to":{"user_id":41,"name":"Anita"},"claimed_by":{"user_id":41,"name":"Anita"},"unread_count":3,"needs_reply":true,"last_message":{"message_id":"wamid.HBgM...C3","sender_type":"customer","sent_at":"2026-10-02T09:06:40.000Z","preview":"Server is still down, please check"},"waiting_since":"2026-10-02T09:06:40.000Z","waiting_seconds":1260,"sla_deadline_at":null,"sla_breached":false,"labels":["vip"],"counts":{"messages":5,"from_customer":3,"from_bot":1}}],"next_cursor":null,"window":{"from":"2026-09-02T00:00:00.000Z","to":"2026-10-02T09:30:00.000Z"},"older_than_seconds":1800}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","contacts","inbox:read","contacts:read"],"note":"Chat content: messages, names and phone numbers."},"x-errors":[{"status":400,"error":"invalid_older_than","when":"older_than is not a duration."}],"x-plan":"scale"}},"/api/reporting/v1/conversations/{phone}":{"get":{"operationId":"getConversation","tags":["Reporting"],"summary":"Get a conversation","description":"One chat, over its whole life (no window), with contact details and hand-over timestamps.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationDetail"},"example":{"phone":"919876543210","contact_name":"Ravi Kumar","state":"HUMAN_ACTIVE","handover_reason":"human_claimed","assigned_to":{"user_id":41,"name":"Anita"},"claimed_by":{"user_id":41,"name":"Anita"},"unread_count":3,"needs_reply":true,"last_message":{"message_id":"wamid.HBgM...C3","sender_type":"customer","sent_at":"2026-10-02T09:06:40.000Z","preview":"Server is still down, please check"},"waiting_since":"2026-10-02T09:06:40.000Z","waiting_seconds":1260,"sla_deadline_at":null,"sla_breached":false,"labels":["vip"],"counts":{"messages":5,"from_customer":3,"from_bot":1},"handover_at":"2026-10-02T09:01:00.000Z","claimed_at":"2026-10-02T09:04:30.000Z","resolved_at":null,"contact":{"name":"Ravi Kumar","email":"ravi@example.com","company":"Kumar Hosting","status":"New","tags":["vip","hosting"]}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","contacts","inbox:read","contacts:read"],"note":"Chat content: messages, names and phone numbers."},"x-errors":[{"status":404,"error":"not_found","when":"No such chat in this workspace, or it is not assigned to a member limited to assigned chats."}],"x-plan":"scale"}},"/api/reporting/v1/conversations/{phone}/messages":{"get":{"operationId":"listMessages","tags":["Reporting"],"summary":"List messages in a conversation","description":"The thread. Every message says who sent it (customer, bot, agent or system), which team member if an agent, and its delivery status. Add expand=receipts to attach the time of each delivery and read tick to outbound messages.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"},{"name":"since","in":"query","description":"Only messages at or after this time.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","description":"End of the window (ISO 8601 or date). Default: now. Alias: until.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"order","in":"query","description":"asc: oldest first. desc: newest first.","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"asc"}},{"name":"expand","in":"query","description":"receipts: attach delivery and read times to outbound messages.","required":false,"schema":{"type":"string","enum":["receipts"]}},{"name":"limit","in":"query","description":"Page size, 1 to 200. Default 100.","required":false,"schema":{"type":"integer","default":100,"minimum":1,"maximum":200}},{"name":"cursor","in":"query","description":"The next_cursor from the previous page. Treat it as opaque.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageList"},"example":{"phone":"919876543210","data":[{"id":1041,"message_id":"wamid.HBgM...C1","sent_at":"2026-10-02T09:00:00.000Z","sender_type":"customer","actor":null,"sender_label":null,"type":"text","text":"My server is down","template_name":null,"media_url":null,"status":"read","source":"whatsapp"},{"id":1044,"message_id":"wamid.HBgM...A1","sent_at":"2026-10-02T09:05:12.000Z","sender_type":"agent","actor":{"user_id":41,"name":"Anita"},"sender_label":"Anita","type":"text","text":"We are looking into it.","template_name":null,"media_url":null,"status":"read","source":"whatsapp","receipts":[{"status":"sent","at":"2026-10-02T09:05:12.000Z"},{"status":"delivered","at":"2026-10-02T09:05:14.000Z"},{"status":"read","at":"2026-10-02T09:05:50.000Z"}]}],"next_cursor":null}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","contacts","inbox:read","contacts:read"],"note":"Chat content: messages, names and phone numbers."},"x-errors":[{"status":400,"error":"invalid_order","when":"order is not asc or desc."}],"x-plan":"scale"}},"/api/reporting/v1/conversations/{phone}/receipts":{"get":{"operationId":"conversationReceipts","tags":["Reporting"],"summary":"Receipts for a conversation","description":"Delivery and read receipts for the outbound messages in a chat, most recent first, in one call. Receipts exist from the day recording began; earlier messages show only their latest status.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"},{"name":"since","in":"query","description":"Only messages at or after this time.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","description":"End of the window (ISO 8601 or date). Default: now. Alias: until.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","description":"Page size, 1 to 200. Default 100.","required":false,"schema":{"type":"integer","default":100,"minimum":1,"maximum":200}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationReceipts"},"example":{"phone":"919876543210","data":[{"message_id":"wamid.HBgM...A1","sent_at":"2026-10-02T09:05:12.000Z","sender_type":"agent","current_status":"read","receipts":[{"status":"sent","at":"2026-10-02T09:05:12.000Z"},{"status":"delivered","at":"2026-10-02T09:05:14.000Z"},{"status":"read","at":"2026-10-02T09:05:50.000Z"}]}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","contacts","inbox:read","contacts:read"],"note":"Chat content: messages, names and phone numbers."},"x-errors":[],"x-plan":"scale"}},"/api/reporting/v1/conversations/{phone}/seen-by":{"get":{"operationId":"seenBy","tags":["Reporting"],"summary":"Who has opened a chat","description":"Which team members opened this chat in the inbox or the embedded panel, when first and last, and how many customer messages each has not yet seen. \"Opened\" means opened in InboxWA, not read on a phone. Recording began on the date in recorded_from.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeenBy"},"example":{"phone":"919876543210","last_customer_message_at":"2026-10-02T09:06:40.000Z","seen_by_anyone":true,"seen_by":[{"user_id":41,"name":"Anita","first_opened_at":"2026-10-02T09:04:30.000Z","last_opened_at":"2026-10-02T09:05:20.000Z","opens":2,"unseen_customer_messages":1},{"user_id":52,"name":"Vikram","first_opened_at":"2026-10-02T09:08:11.000Z","last_opened_at":"2026-10-02T09:08:11.000Z","opens":1,"unseen_customer_messages":0}],"recorded_from":"2026-09-30T04:00:00.000Z"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","team_management","analytics","inbox:read","team_management:read","analytics:read"],"note":"Team accountability data: who opened, who replied, how fast."},"x-errors":[],"x-plan":"scale"}},"/api/reporting/v1/conversations/{phone}/views":{"get":{"operationId":"listViews","tags":["Reporting"],"summary":"Open log for a chat","description":"Every recorded open of this chat, newest first: who opened it, when, and how many unread customer messages that open cleared. Repeated opens by the same member within a minute count once.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"},{"name":"limit","in":"query","description":"Page size, 1 to 200. Default 100.","required":false,"schema":{"type":"integer","default":100,"minimum":1,"maximum":200}},{"name":"cursor","in":"query","description":"The next_cursor from the previous page. Treat it as opaque.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Views"},"example":{"phone":"919876543210","data":[{"user":{"user_id":52,"name":"Vikram"},"opened_at":"2026-10-02T09:08:11.000Z","marked_read":1},{"user":{"user_id":41,"name":"Anita"},"opened_at":"2026-10-02T09:04:30.000Z","marked_read":2}],"next_cursor":null}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","team_management","analytics","inbox:read","team_management:read","analytics:read"],"note":"Team accountability data: who opened, who replied, how fast."},"x-errors":[],"x-plan":"scale"}},"/api/reporting/v1/conversations/{phone}/replies":{"get":{"operationId":"conversationReplies","tags":["Reporting"],"summary":"Replies in a conversation","description":"Every outbound message with who sent it and how long the customer had waited. A bot usually answers in seconds, so there are two clocks: a bot or system reply is timed from the moment the customer began waiting; an agent reply from the moment the customer began waiting for a person. first_human_response is the number to hold a team to. A send that failed never counts as a reply.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"},{"name":"since","in":"query","description":"Only messages at or after this time.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","description":"End of the window (ISO 8601 or date). Default: now. Alias: until.","required":false,"schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationReplies"},"example":{"phone":"919876543210","first_customer_message_at":"2026-10-02T09:00:00.000Z","replies":[{"message_id":"wamid.HBgM...B1","sent_at":"2026-10-02T09:00:04.000Z","sender_type":"bot","actor":null,"sender_label":null,"answers_message_id":"wamid.HBgM...C1","status":"delivered","response_seconds":4},{"message_id":"wamid.HBgM...A1","sent_at":"2026-10-02T09:05:12.000Z","sender_type":"agent","actor":{"user_id":41,"name":"Anita"},"sender_label":"Anita","answers_message_id":"wamid.HBgM...C1","status":"read","response_seconds":312}],"first_response":{"message_id":"wamid.HBgM...B1","sender_type":"bot","actor":null,"response_seconds":4},"first_human_response":{"message_id":"wamid.HBgM...A1","sender_type":"agent","actor":{"user_id":41,"name":"Anita"},"response_seconds":312}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","team_management","analytics","inbox:read","team_management:read","analytics:read"],"note":"Team accountability data: who opened, who replied, how fast."},"x-errors":[],"x-plan":"scale"}},"/api/reporting/v1/conversations/{phone}/assignment-history":{"get":{"operationId":"assignmentHistory","tags":["Reporting"],"summary":"Who has held a chat","description":"Claims, releases, transfers and resolutions, newest first. From detailed_since the record is exact; before that it is rebuilt from the team audit log, which has claims, releases and transfers but not resolutions (source: audit).","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignmentHistory"},"example":{"phone":"919876543210","data":[{"event":"transferred","at":"2026-10-02T09:40:11.000Z","from":{"user_id":41,"name":"Anita"},"to":{"user_id":52,"name":"Vikram"},"by":{"user_id":41,"name":"Anita"},"reason":"handoff_transfer","source":"events"},{"event":"claimed","at":"2026-10-02T09:04:30.000Z","from":null,"to":{"user_id":41,"name":"Anita"},"by":{"user_id":41,"name":"Anita"},"reason":"human_claimed","source":"events"}],"detailed_since":"2026-09-30T04:00:00.000Z"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","team_management","analytics","inbox:read","team_management:read","analytics:read"],"note":"Team accountability data: who opened, who replied, how fast."},"x-errors":[],"x-plan":"scale"}},"/api/reporting/v1/messages":{"get":{"operationId":"listMessagesByStatus","tags":["Reporting"],"summary":"Messages by delivery status","description":"Outbound messages currently in a status, newest first. status=failed is the delivery-problem report: each row carries the error text recorded at send time. The window defaults to 7 days, up to 90.","parameters":[{"name":"status","in":"query","description":"Required. The delivery status to list.","required":true,"schema":{"type":"string","enum":["sent","delivered","read","failed","pending"]},"example":"failed"},{"name":"since","in":"query","description":"Start of the window: an ISO 8601 timestamp or a date (YYYY-MM-DD, start of that UTC day). Default: 7 days ago. Alias: from.","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-10-01"},{"name":"to","in":"query","description":"End of the window (ISO 8601 or date). Default: now. Alias: until.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"phone","in":"query","description":"Only this contact.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Page size, 1 to 200. Default 50.","required":false,"schema":{"type":"integer","default":50,"minimum":1,"maximum":200}},{"name":"cursor","in":"query","description":"The next_cursor from the previous page. Treat it as opaque.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FailedMessageList"},"example":{"data":[{"id":1102,"message_id":"wamid.HBgM...F1","phone":"919876543210","sent_at":"2026-10-02T08:12:00.000Z","sender_type":"system","actor":null,"sender_label":null,"type":"template","template_name":"renewal_reminder","media_url":null,"status":"failed","source":null,"error":"#131047 Re-engagement message"}],"next_cursor":null,"window":{"from":"2026-09-25T09:30:00.000Z","to":"2026-10-02T09:30:00.000Z"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","contacts","inbox:read","contacts:read"],"note":"Chat content: messages, names and phone numbers."},"x-errors":[{"status":400,"error":"invalid_status","when":"status is missing or not recognised."},{"status":400,"error":"window_too_large","when":"The window is longer than 90 days."}],"x-plan":"scale"}},"/api/reporting/v1/messages/{id}":{"get":{"operationId":"getMessage","tags":["Reporting"],"summary":"Get a message","description":"One message by its WhatsApp message id (wamid), with its current delivery status and, for a failed send, the reason.","parameters":[{"name":"id","in":"path","required":true,"description":"WhatsApp message id.","schema":{"type":"string"},"example":"wamid.HBgM...A1"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageDetail"},"example":{"id":1044,"message_id":"wamid.HBgM...A1","sent_at":"2026-10-02T09:05:12.000Z","sender_type":"agent","actor":{"user_id":41,"name":"Anita"},"sender_label":"Anita","type":"text","text":"We are looking into it.","template_name":null,"media_url":null,"status":"read","source":"whatsapp","phone":"919876543210","error":null}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","contacts","inbox:read","contacts:read"],"note":"Chat content: messages, names and phone numbers."},"x-errors":[{"status":404,"error":"not_found","when":"No such message in this workspace."}],"x-plan":"scale"}},"/api/reporting/v1/messages/{id}/receipts":{"get":{"operationId":"messageReceipts","tags":["Reporting"],"summary":"Delivery and read receipts","description":"Every status change of one message with the time WhatsApp reported it: sent, delivered, read. A read receipt arrives only if the customer has read receipts switched on; WhatsApp reports nothing otherwise. Receipts exist from recorded_from; messages before that keep only their latest status.","parameters":[{"name":"id","in":"path","required":true,"description":"WhatsApp message id.","schema":{"type":"string"},"example":"wamid.HBgM...A1"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageReceipts"},"example":{"message_id":"wamid.HBgM...A1","phone":"919876543210","current_status":"read","receipts":[{"status":"sent","at":"2026-10-02T09:05:12.000Z"},{"status":"delivered","at":"2026-10-02T09:05:14.000Z"},{"status":"read","at":"2026-10-02T09:05:50.000Z"}],"recorded_from":"2026-10-02T09:05:12.000Z"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","contacts","inbox:read","contacts:read"],"note":"Chat content: messages, names and phone numbers."},"x-errors":[{"status":404,"error":"not_found","when":"No such message in this workspace."}],"x-plan":"scale"}},"/api/reporting/v1/replies":{"get":{"operationId":"listReplies","tags":["Reporting"],"summary":"Replies across the workspace","description":"Outbound messages in the window, newest first, filterable by team member and sender type, each with its response time. Use it to audit one person, or to see what the bot answered.","parameters":[{"name":"since","in":"query","description":"Start of the window: an ISO 8601 timestamp or a date (YYYY-MM-DD, start of that UTC day). Default: 7 days ago. Alias: from.","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-10-01"},{"name":"to","in":"query","description":"End of the window (ISO 8601 or date). Default: now. Alias: until.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"agent_id","in":"query","description":"Only replies sent by this team member.","required":false,"schema":{"type":"integer"}},{"name":"sender_type","in":"query","description":"Only this kind of sender.","required":false,"schema":{"type":"string","enum":["agent","bot","system"]}},{"name":"phone","in":"query","description":"Only this contact.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Page size, 1 to 200. Default 50.","required":false,"schema":{"type":"integer","default":50,"minimum":1,"maximum":200}},{"name":"cursor","in":"query","description":"The next_cursor from the previous page. Treat it as opaque.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyList"},"example":{"data":[{"message_id":"wamid.HBgM...A1","phone":"919876543210","sent_at":"2026-10-02T09:05:12.000Z","sender_type":"agent","actor":{"user_id":41,"name":"Anita"},"sender_label":"Anita","status":"read","response_seconds":312}],"next_cursor":null,"window":{"from":"2026-09-25T09:30:00.000Z","to":"2026-10-02T09:30:00.000Z"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","team_management","analytics","inbox:read","team_management:read","analytics:read"],"note":"Team accountability data: who opened, who replied, how fast."},"x-errors":[{"status":400,"error":"invalid_agent_id","when":"agent_id is not a user id."},{"status":400,"error":"invalid_sender_type","when":"sender_type is not agent, bot or system."}],"x-plan":"scale"}},"/api/reporting/v1/metrics/first-response":{"get":{"operationId":"firstResponseMetrics","tags":["Reporting"],"summary":"First response times","description":"How long customers waited for a first reply, and for a first human reply, across the workspace: count, median, 90th percentile and mean. An episode is a wait that begins with the first customer message in a chat or the first after any outbound message. Group by day (in a time zone you choose) or by the team member who gave the first human reply.","parameters":[{"name":"since","in":"query","description":"Start of the window: an ISO 8601 timestamp or a date (YYYY-MM-DD, start of that UTC day). Default: 7 days ago. Alias: from.","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-10-01"},{"name":"to","in":"query","description":"End of the window (ISO 8601 or date). Default: now. Alias: until.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"group_by","in":"query","description":"Break the result down.","required":false,"schema":{"type":"string","enum":["none","day","agent"],"default":"none"}},{"name":"tz","in":"query","description":"IANA time zone for day grouping. Default UTC.","required":false,"schema":{"type":"string"},"example":"Asia/Kolkata"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FirstResponseMetrics"},"example":{"window":{"from":"2026-09-25T09:30:00.000Z","to":"2026-10-02T09:30:00.000Z"},"time_zone":"Asia/Kolkata","episodes":5,"unanswered_episodes":2,"first_response":{"count":3,"median_seconds":100,"p90_seconds":241,"mean_seconds":121},"first_human_response":{"count":3,"median_seconds":252,"p90_seconds":304,"mean_seconds":221},"truncated":false,"groups":[{"agent":{"user_id":41,"name":"Anita"},"first_human_response":{"count":2,"median_seconds":282,"p90_seconds":309,"mean_seconds":282}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","team_management","analytics","inbox:read","team_management:read","analytics:read"],"note":"Team accountability data: who opened, who replied, how fast."},"x-errors":[{"status":400,"error":"invalid_group_by","when":"group_by is not none, day or agent."},{"status":400,"error":"invalid_tz","when":"tz is not an IANA time zone."},{"status":403,"error":"workspace_metrics_restricted","when":"The caller is limited to assigned chats, so workspace-wide metrics are not available."}],"x-plan":"scale"}},"/api/reporting/v1/team/{userId}/unseen":{"get":{"operationId":"unseenForMember","tags":["Reporting"],"summary":"Chats a team member has not opened","description":"Chats assigned to this team member that have customer messages the member has not opened since they arrived, most recent first. ever_opened_by_user is false for chats the member has never opened.","parameters":[{"name":"userId","in":"path","required":true,"description":"Team member user id.","schema":{"type":"integer"},"example":41},{"name":"since","in":"query","description":"Start of the window: an ISO 8601 timestamp or a date (YYYY-MM-DD, start of that UTC day). Default: 30 days ago. Alias: from.","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-10-01"},{"name":"to","in":"query","description":"End of the window (ISO 8601 or date). Default: now. Alias: until.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","description":"Page size, 1 to 200. Default 50.","required":false,"schema":{"type":"integer","default":50,"minimum":1,"maximum":200}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Unseen"},"example":{"user":{"user_id":41,"name":"Anita"},"window":{"from":"2026-09-02T00:00:00.000Z","to":"2026-10-02T09:30:00.000Z"},"data":[{"phone":"919876543210","contact_name":"Ravi Kumar","unseen_customer_messages":1,"last_customer_message_at":"2026-10-02T09:06:40.000Z","last_opened_at":"2026-10-02T09:05:20.000Z","ever_opened_by_user":true}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","team_management","analytics","inbox:read","team_management:read","analytics:read"],"note":"Team accountability data: who opened, who replied, how fast."},"x-errors":[{"status":400,"error":"invalid_user_id","when":"userId is not a number."},{"status":404,"error":"not_found","when":"No such team member in this workspace."}],"x-plan":"scale"}},"/api/api-keys":{"get":{"operationId":"listApiKeys","tags":["API keys"],"summary":"List keys","description":"Every key in the workspace with its scopes, expiry, rate limit and last use. The plaintext is never returned again after creation.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyList"},"example":{"keys":[{"id":7,"label":"Dashboard","last_4":"x9Kq","created_at":"2026-09-25T10:00:00.000Z","last_used_at":"2026-10-02T09:29:00.000Z","revoked_at":null,"scopes":["inbox:read","team_management:read"],"read_only":true,"expires_at":"2026-12-24T10:00:00.000Z","expired":false,"rate_limit_per_min":120,"effective_rate_limit_per_min":120,"rotated_from":null,"created_by_name":"admin@example.com","revoked_by_name":null}],"defaults":{"rate_limit_per_min":600,"max_keys":20},"api_access":true,"api_tier":"full"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["settings","settings:read"],"note":"Read access for members with the settings permission."},"x-errors":[],"x-plan":"scale"},"post":{"operationId":"createApiKey","tags":["API keys"],"summary":"Create a key","description":"Mints a key. The plaintext is in the response and cannot be shown again. Only workspace admins can create keys: in the dashboard, or with a full-access key created by an admin. Scopes may include the :read variants, which make a key that can call GET endpoints and can never change anything.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["label"],"properties":{"label":{"type":"string","maxLength":64,"description":"A name to recognise the key by."},"scopes":{"type":"array","items":{"type":"string"},"description":"all, or any of the scopes from GET /api/api-keys/scopes. Default: all."},"expires_in_days":{"type":"integer","minimum":1,"maximum":730,"description":"Expire after this many days. Or use expires_at."},"expires_at":{"type":"string","format":"date-time","description":"A future expiry, within 730 days."},"rate_limit_per_min":{"type":"integer","minimum":10,"maximum":100000,"description":"Per-key requests per minute. Omit for the workspace default."}}},"example":{"label":"Dashboard","scopes":["inbox:read","team_management:read"],"expires_in_days":90,"rate_limit_per_min":120}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"},"example":{"id":7,"label":"Dashboard","key":"iwk_2vN0k1...x9Kq","last_4":"x9Kq","scopes":["inbox:read","team_management:read"],"read_only":true,"expires_at":"2026-12-24T10:00:00.000Z","rate_limit_per_min":120,"created_at":"2026-09-25T10:00:00.000Z","message":"Save this key now. It can’t be shown again."}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":[],"note":"Workspace admin: a dashboard session, or a full-access key created by an admin. A scoped key is refused with scope_denied."},"x-errors":[{"status":400,"error":"label_required","when":"No label."},{"status":400,"error":"invalid_scopes","when":"No valid scope in the list."},{"status":400,"error":"invalid_expiry","when":"Expiry outside 1 to 730 days."},{"status":400,"error":"invalid_rate_limit","when":"Limit outside 10 to 100000."},{"status":400,"error":"too_many_keys","when":"The workspace already has 20 active keys."}],"x-plan":"scale"}},"/api/api-keys/scopes":{"get":{"operationId":"apiKeyScopes","tags":["API keys"],"summary":"List scopes","description":"The scopes a key can be limited to, grouped, with a description of each and the :read variant where one exists. The panel renders from this, so a new scope needs no client change.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScopeCatalog"},"example":{"groups":{"workspace":"Workspace","ai":"AI agent","admin":"Admin and billing","channel":"Channel"},"scopes":[{"key":"inbox","group":"workspace","label":"Inbox","description":"Read chats and messages; claim, release, transfer, snooze, label and note conversations.","readScope":"inbox:read"}],"readScopes":["inbox:read","contacts:read"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["settings","settings:read"],"note":null},"x-errors":[],"x-plan":"scale"}},"/api/api-keys/{id}":{"patch":{"operationId":"updateApiKey","tags":["API keys"],"summary":"Update a key","description":"Rename a key, or change its expiry or rate limit. Send null for expires_at or rate_limit_per_min to clear them. Scopes cannot be changed: rotate the key instead, so an old secret can never quietly become more powerful.","parameters":[{"name":"id","in":"path","required":true,"description":"Key id.","schema":{"type":"integer"},"example":7}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string"},"expires_in_days":{"type":"integer","nullable":true},"expires_at":{"type":"string","format":"date-time","nullable":true},"rate_limit_per_min":{"type":"integer","nullable":true}}},"example":{"label":"Dashboard (prod)","rate_limit_per_min":300}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":[],"note":"Workspace admin: a dashboard session, or a full-access key created by an admin. A scoped key is refused with scope_denied."},"x-errors":[{"status":400,"error":"nothing_to_update","when":"The body changed nothing."},{"status":404,"error":"not_found","when":"No such key in this workspace."},{"status":400,"error":"already_revoked","when":"The key is revoked."}],"x-plan":"scale"},"delete":{"operationId":"revokeApiKey","tags":["API keys"],"summary":"Revoke a key","description":"Ends a key immediately. The row is kept so its audit trail survives.","parameters":[{"name":"id","in":"path","required":true,"description":"Key id.","schema":{"type":"integer"},"example":7}],"responses":{"200":{"description":"Revoked.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":[],"note":"Workspace admin: a dashboard session, or a full-access key created by an admin. A scoped key is refused with scope_denied."},"x-errors":[{"status":404,"error":"not_found","when":"No such key."},{"status":400,"error":"already_revoked","when":"Already revoked."}],"x-plan":"scale"}},"/api/api-keys/{id}/rotate":{"post":{"operationId":"rotateApiKey","tags":["API keys"],"summary":"Rotate a key","description":"Issues a replacement with the same scopes, limit and label, and ends the old key. Set grace_hours (up to 168) to keep the old key valid while a deployed integration switches over; 0 revokes it at once. The new plaintext is shown once.","parameters":[{"name":"id","in":"path","required":true,"description":"Key id.","schema":{"type":"integer"},"example":7}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"grace_hours":{"type":"integer","minimum":0,"maximum":168}}},"example":{"grace_hours":24}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyRotated"},"example":{"id":8,"label":"Dashboard","key":"iwk_9pQ2m8...t3Lw","last_4":"t3Lw","scopes":["inbox:read","team_management:read"],"rotated_from":7,"old_key_valid_until":"2026-10-03T10:00:00.000Z","message":"Save this key now. It can’t be shown again."}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":[],"note":"Workspace admin: a dashboard session, or a full-access key created by an admin. A scoped key is refused with scope_denied."},"x-errors":[{"status":400,"error":"invalid_grace","when":"grace_hours outside 0 to 168."},{"status":404,"error":"not_found","when":"No such key."},{"status":400,"error":"already_revoked","when":"The key is already revoked."}],"x-plan":"scale"}},"/api/api-keys/{id}/usage":{"get":{"operationId":"apiKeyUsage","tags":["API keys"],"summary":"Key activity","description":"Calls, errors, a daily series, top endpoints and the last 50 calls for one key. Refused and rate-limited calls are included. The log keeps 30 days.","parameters":[{"name":"id","in":"path","required":true,"description":"Key id.","schema":{"type":"integer"},"example":7},{"name":"range","in":"query","description":"Window.","required":false,"schema":{"type":"string","enum":["7d","30d"],"default":"7d"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyUsage"},"example":{"range":"7d","totals":{"calls":2104,"errors":3,"forbidden":1,"serverErrors":0},"daily":[{"date":"2026-10-01","calls":310,"errors":0}],"topEndpoints":[{"method":"GET","path":"/api/reporting/v1/conversations","calls":1800,"errors":0}],"recent":[{"id":99021,"ts":"2026-10-02T09:29:10.000Z","method":"GET","path":"/api/reporting/v1/conversations","status":200,"ip":"203.0.113.7"}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["settings","settings:read"],"note":null},"x-errors":[],"x-plan":"scale"}},"/api/webhook/outbound/config":{"get":{"operationId":"getWebhookConfig","tags":["Webhooks"],"summary":"Get webhook settings","description":"The saved endpoint and subscribed events, plus the catalogue of every event with whether this workspace can subscribe to it.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookConfig"},"example":{"configured":true,"endpoint_url":"https://example.com/hooks/inboxwa","events":["message_received","message_status"],"enabled":true,"updated_at":"2026-09-25T10:00:00.000Z","updated_by_name":"admin@example.com","valid_events":["message_received","message_status"],"api_tier":"full","webhooks_available":true,"event_catalog":[{"event":"message_status","group":"Messages","plan":"scale","description":"An outbound message is delivered, read or fails, with the WhatsApp timestamp.","available":true}],"credit_alert_levels":[80,100]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["settings","settings:read"],"note":null},"x-errors":[],"x-plan":"scale"},"put":{"operationId":"putWebhookConfig","tags":["Webhooks"],"summary":"Save webhook settings","description":"Registers one HTTPS endpoint and the events to send. The signing secret is returned on the first save only. Scale events are refused for a workspace without API access, so nothing is subscribed that would never fire. Changes apply immediately.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["endpoint_url","events"],"properties":{"endpoint_url":{"type":"string","format":"uri","description":"http or https endpoint that accepts POST."},"events":{"type":"array","items":{"type":"string"},"description":"One or more event names from the catalogue."},"enabled":{"type":"boolean","description":"Default true."},"credit_alert_levels":{"type":"array","items":{"type":"integer"},"description":"Percent levels for credits_threshold. Default [80, 100]."}}},"example":{"endpoint_url":"https://example.com/hooks/inboxwa","events":["message_received","message_status","credits_threshold"],"credit_alert_levels":[80,100]}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"endpoint_url":"https://example.com/hooks/inboxwa","events":["message_received","message_status"],"enabled":true,"signing_secret":"9f2c...e41a","message":"Outbound webhook configured. Save the signing secret -- it will not be shown again."}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":[],"note":"Workspace admin: a dashboard session, or a full-access key created by an admin. A scoped key is refused with scope_denied."},"x-errors":[{"status":400,"error":"endpoint_url_required","when":"No endpoint."},{"status":400,"error":"invalid_url","when":"Not an http or https URL."},{"status":400,"error":"events_required","when":"No events."},{"status":403,"error":"event_requires_scale","when":"A chosen event needs API access on the plan."}],"x-plan":"scale"}},"/api/webhook/outbound/rotate-secret":{"post":{"operationId":"rotateWebhookSecret","tags":["Webhooks"],"summary":"Rotate the signing secret","description":"Replaces the signing secret and returns the new one once. Update your receiver before the next delivery.","responses":{"200":{"description":"Rotated.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"signing_secret":"41ab...07cd","message":"Signing secret rotated. Save it now; it will not be shown again."}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":[],"note":"Workspace admin: a dashboard session, or a full-access key created by an admin. A scoped key is refused with scope_denied."},"x-errors":[{"status":404,"error":"not_configured","when":"No webhook is configured."}],"x-plan":"scale"}},"/api/webhook/outbound/test":{"post":{"operationId":"testWebhook","tags":["Webhooks"],"summary":"Send a test event","description":"Sends one signed event to the saved endpoint now and reports what it answered. The payload is the catalogue example, marked with test: true. It ignores your subscriptions and plan, so it checks connectivity and signature verification, not entitlement.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"event":{"type":"string","description":"A catalogue event. Default message_received."}}},"example":{"event":"message_status"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestResult"},"example":{"ok":true,"status":200,"attempts":1,"error":null,"duration_ms":184,"event":"message_status"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":[],"note":"Workspace admin: a dashboard session, or a full-access key created by an admin. A scoped key is refused with scope_denied."},"x-errors":[{"status":400,"error":"unknown_event","when":"Not a catalogue event."},{"status":404,"error":"not_configured","when":"Save an endpoint first."}],"x-plan":"scale"}},"/api/webhook/outbound/deliveries":{"get":{"operationId":"webhookDeliveries","tags":["Webhooks"],"summary":"Delivery log","description":"The most recent deliveries with their outcome, so \"is my endpoint actually receiving events\" has an answer.","parameters":[{"name":"limit","in":"query","description":"Page size, 1 to 50. Default 15.","required":false,"schema":{"type":"integer","default":15,"minimum":1,"maximum":50}}],"responses":{"200":{"description":"Deliveries, newest first.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"deliveries":[{"event_type":"message_status","status":"success","summary":"message_status -> https://example.com/hooks/inboxwa","error_message":null,"created_at":"2026-10-02T09:22:49.000Z"}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":[],"note":"Any member of the workspace."},"x-errors":[],"x-plan":"scale"}},"/api/agent-hub/credits":{"get":{"operationId":"getCredits","tags":["Credits"],"summary":"Credit balance","description":"The plan, the current calendar-month cycle (resets on the 1st, India time), credits used, the allocation (bundle plus active top-ups), remaining credits and any overage. Also returns usage by feature. Requires the AI agent to be enabled for the workspace.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Credits"},"example":{"plan":{"key":"growth","label":"Growth","monthly_inr":2499,"credits_allocation":2500,"overage_inr_per_credit":0.2},"cycle":{"start":"2026-09-30T18:30:00.000Z","end":"2026-10-02T09:30:00.000Z","kind":"calendar_month"},"used_credits":3120.4,"allocation":12500,"topup_credits":10000,"remaining_credits":9379.6,"overage_credits":0,"overage_inr":0}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":[],"note":"Any authenticated caller in a workspace with the AI agent enabled."},"x-errors":[],"x-plan":"scale"}},"/api/agent-hub/credits/ledger":{"get":{"operationId":"getCreditsLedger","tags":["Credits"],"summary":"Credit ledger","description":"Every AI action this cycle with its feature, tokens and the credits it cost, newest first. One credit is 5,000 tokens.","parameters":[{"name":"limit","in":"query","description":"Page size, 1 to 200. Default 50.","required":false,"schema":{"type":"integer","default":50,"minimum":1,"maximum":200}},{"name":"offset","in":"query","description":"Rows to skip.","required":false,"schema":{"type":"integer","default":0,"minimum":0}},{"name":"feature","in":"query","description":"Only this feature key.","required":false,"schema":{"type":"string"}},{"name":"channel","in":"query","description":"Only this channel: whatsapp, website, workflows, marketing or internal.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Ledger rows.","content":{"application/json":{"schema":{"type":"object"},"example":{"events":[{"id":88123,"created_at":"2026-10-02T09:14:07.000Z","feature":"chat_reply","label":"AI chat reply","chat_id":"919876543210","detail":null,"input_tokens":3200,"cached_tokens":2600,"output_tokens":180,"audio_seconds":0,"credits":0.29}],"total":1,"limit":50,"offset":0}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":[],"note":"Any authenticated caller in a workspace with the AI agent enabled."},"x-errors":[],"x-plan":"scale"}},"/api/send-message":{"post":{"operationId":"sendMessage","tags":["Messaging"],"summary":"Send a message","description":"Sends one message to one contact through the WhatsApp Cloud API: text, a template, or an image, video, audio or document. Free-form text and media can only be sent within 24 hours of the customer’s last message; outside that window send a template. For a chat imported from WhatsApp Web only text is supported, and the response carries channel: whatsapp_web. Subject to the workspace’s consent, assignment and channel rules. Not held to the default per-key request limit. A 500 here usually means WhatsApp refused the message, not that we failed: read errorCode before retrying (see the Errors guide).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to"],"properties":{"to":{"type":"string","description":"Recipient phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added)."},"type":{"type":"string","enum":["text","template","image","video","audio","document"],"description":"Default text."},"message":{"type":"string","description":"The text, when type is text."},"template":{"type":"object","description":"When type is template.","required":["name"],"properties":{"name":{"type":"string","description":"Approved template name. Required."},"language":{"type":"string","description":"Language code such as en_US. Looked up from the approved template when omitted."},"parameters":{"type":"array","items":{"type":"string"},"description":"Values for the body variables, in order."},"headerImage":{"type":"string","description":"Public URL of a header image."},"headerImageId":{"type":"string","description":"Uploaded media id of a header image."}}},"mediaUrl":{"type":"string","description":"Public URL of the media, for image, video, audio and document. Send this or mediaId."},"mediaId":{"type":"string","description":"A media id you uploaded to WhatsApp. Send this or mediaUrl."},"caption":{"type":"string","description":"Caption for image, video and document."},"filename":{"type":"string","description":"File name shown for a document."},"senderId":{"type":"string","description":"WhatsApp phone number id to send from. Default: the workspace’s main number."},"confirmPolicy":{"type":"string","description":"Send again with inbox.reply.sensitive_guard after a 403 policy_confirm, once the person has confirmed the text is meant to go out."}}},"example":{"to":"919876543210","type":"template","template":{"name":"order_confirmation","language":"en_US","parameters":["ORD-4271","2026-10-05"]}}}}},"responses":{"200":{"description":"Accepted by WhatsApp. Delivery and read progress arrive as message_status webhooks, or read them from the receipts calls.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"messageId":"wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgSQTEB","data":{"messaging_product":"whatsapp","contacts":[{"input":"919876543210","wa_id":"919876543210"}],"messages":[{"id":"wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgSQTEB"}]}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["whatsapp_api"],"note":"The workspace must also have the WhatsApp Cloud API enabled (403 feature_disabled_for_org otherwise)."},"x-errors":[{"status":400,"error":"Phone number is required","when":"to is missing."},{"status":400,"error":"Message text is required","when":"type is text and message is empty."},{"status":400,"error":"Template name is required","when":"type is template and template.name is missing."},{"status":400,"error":"mediaId or mediaUrl is required for media messages","when":"A media type without media."},{"status":403,"error":"feature_disabled_for_org","when":"The WhatsApp Cloud API is switched off for the workspace."},{"status":403,"error":"not_assigned","when":"The caller is limited to assigned chats and this one is not assigned to them."},{"status":403,"error":"policy_denied","when":"A workspace policy refused the send: someone else is handling the chat (reply lock), the file type is not allowed, the text contains sensitive data the workspace blocks, or the sender holds the most chats allowed. policy names the rule and message says why."},{"status":403,"error":"policy_confirm","when":"The text looks like it contains a card, Aadhaar or PAN number and the workspace asks for confirmation. Send again with confirmPolicy set to the policy named in the reply."},{"status":500,"error":"WhatsApp’s own message","when":"WhatsApp refused the send. errorCode, errorTitle, errorSubcode and errorDetails carry its reason, for example 131047 for a closed 24-hour window. Retrying the same request will fail the same way."}],"x-plan":"growth"}},"/api/bulk-send":{"post":{"operationId":"bulkSend","tags":["Messaging"],"summary":"Send a broadcast","description":"Sends one message or template to many numbers as a campaign. Numbers that are blacklisted, opted out, or over the workspace’s frequency cap are skipped and counted in the response. The call answers 202 at once and the sending runs in the background, one recipient at a time with a delay between them; a broadcast can also be scheduled, or held for approval when the workspace requires it. Not held to the default per-key request limit; volume is governed by your credits and WhatsApp’s own limits.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["numbers"],"properties":{"numbers":{"type":"array","items":{"type":"string"},"description":"Recipient phone numbers."},"type":{"type":"string","enum":["text","template","image","video","document"],"description":"Default text."},"message":{"type":"string","description":"The text or caption."},"template":{"type":"object","description":"Template name, language and header, as for a single send."},"variableMapping":{"type":"object","description":"Maps a template variable position to a contact field, for example {\"1\": \"contact.name\"}."},"mediaUrl":{"type":"string","description":"Public URL of the media. Required for image, video and document."},"campaignName":{"type":"string","description":"A name to find the campaign by."},"scheduledAt":{"type":"string","format":"date-time","description":"Send later, at this time."},"senderId":{"type":"string","description":"WhatsApp phone number id to send from."},"trackLinks":{"type":"boolean","description":"Track clicks on links in the message."},"delayMs":{"type":"integer","minimum":250,"maximum":5000,"description":"Pause between recipients. Default 1000."},"enableRetry":{"type":"boolean","description":"Retry a failed recipient. Default true."},"maxRetries":{"type":"integer","minimum":0,"maximum":5,"description":"Retries per recipient. Default 2."}}},"example":{"numbers":["919876543210","919812345678"],"type":"template","template":{"name":"diwali_offer","language":"en_US"},"campaignName":"Diwali offer","variableMapping":{"1":"contact.name"}}}}},"responses":{"200":{"description":"Accepted (202). status is processing, scheduled (with scheduledAt), or pending_approval.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"status":"processing","campaignId":3812,"message":"Broadcast accepted and is executing in the background.","totalRecipients":2,"skippedOptOut":0,"skippedBlacklist":0}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["whatsapp_api"],"note":"The workspace must also have the WhatsApp Cloud API enabled."},"x-errors":[{"status":400,"error":"Phone numbers array is required","when":"numbers is missing or empty."},{"status":400,"error":"mediaUrl is required for media broadcasts","when":"An image, video or document broadcast without mediaUrl."},{"status":400,"error":"All recipients are blacklisted, opted out, or over the frequency cap","when":"Nothing left to send. skippedOptOut, skippedBlacklist and skippedFrequencyCap say why."},{"status":403,"error":"feature_disabled_for_org","when":"The WhatsApp Cloud API is switched off for the workspace."}],"x-plan":"growth"}},"/api/templates":{"get":{"operationId":"listTemplates","tags":["Messaging"],"summary":"List message templates","description":"The workspace’s WhatsApp message templates as WhatsApp holds them, up to 100, each with its name, status, category, language and components. To send a template message, use the name and language of a template whose status is APPROVED.","responses":{"200":{"description":"The templates.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"templates":[{"id":"1234567890123456","name":"order_confirmation","status":"APPROVED","category":"UTILITY","language":"en_US","components":[{"type":"BODY","text":"Your order {{1}} ships on {{2}}."}]}],"total":1}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":[],"note":"Any valid key, on Growth and Scale. The workspace must have WhatsApp connected."},"x-errors":[{"status":403,"error":"not_connected","when":"WhatsApp is not connected for this workspace."},{"status":500,"error":"Failed to fetch templates","when":"WhatsApp did not answer."}],"x-plan":"growth"}},"/api/contacts/paginated":{"get":{"operationId":"listContacts","tags":["Contacts"],"summary":"List contacts","description":"Contacts, newest activity first, with pagination. Each contact has phone, name, email, company, status (New by default), tags (one comma-separated string), notes, chat_status, custom_attributes (an object), last_msg_time, claimed_by and claimed_by_name, plus the columns the app shows. search matches name, phone, tags and message text. For an API key this is part of the full API (Scale plan).","parameters":[{"name":"page","in":"query","description":"Page number from 1.","required":false,"schema":{"type":"integer","default":1,"minimum":1}},{"name":"limit","in":"query","description":"Page size. Default 50.","required":false,"schema":{"type":"integer","default":50}},{"name":"search","in":"query","description":"Search name, phone, tags and message text.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A page of contacts.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"contacts":[{"phone":"919876543210","name":"Ravi Kumar","email":"ravi@example.com","company":"Kumar Hosting","status":"New","tags":"vip,hosting","chat_status":"open","custom_attributes":{"plan":"Business"},"last_msg_time":"2026-10-02T09:06:40.000Z"}],"meta":{"total":14970,"page":1,"limit":50,"hasMore":true}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts","contacts:read"],"note":null},"x-errors":[],"x-plan":"scale"}},"/api/contacts/search":{"get":{"operationId":"searchContacts","tags":["Contacts"],"summary":"Search contacts","description":"Finds contacts by name, phone number or tag. Returns up to 50. An empty q returns an empty list.","parameters":[{"name":"q","in":"query","description":"The text to look for.","required":true,"schema":{"type":"string"},"example":"ravi"}],"responses":{"200":{"description":"Matches.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"contacts":[{"phone":"919876543210","name":"Ravi Kumar","tags":"vip,hosting"}],"total":1}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts","contacts:read"],"note":null},"x-errors":[],"x-plan":"scale"}},"/api/contacts":{"get":{"operationId":"listAllContacts","tags":["Contacts"],"summary":"List contacts (plain array)","description":"Contacts as a plain JSON array rather than a page object, with an optional search and limit. Use List contacts for anything large.","parameters":[{"name":"search","in":"query","description":"Search name, phone and tags.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Maximum contacts to return.","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"A JSON array of contacts.","content":{"application/json":{"schema":{"type":"object"},"example":[{"phone":"919876543210","name":"Ravi Kumar","status":"New","tags":"vip,hosting"}]}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts","contacts:read"],"note":null},"x-errors":[],"x-plan":"scale"},"post":{"operationId":"createContact","tags":["Contacts"],"summary":"Create or update a contact","description":"Saves a contact by phone number. A number that already exists is updated, and any extra fields you send are merged into its custom attributes. The phone number is normalised and must be a valid mobile number.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone"],"properties":{"phone":{"type":"string","description":"The contact’s phone number with the country code. A 10-digit number with no country code is treated as Indian (91 is added)."},"name":{"type":"string","description":"Display name."},"email":{"type":"string","description":"Email address."},"company":{"type":"string","description":"Company."},"tags":{"type":"string","description":"Comma-separated tags, for example \"vip,hosting\"."},"status":{"type":"string","description":"CRM pipeline status."}}},"example":{"phone":"919876543210","name":"Ravi Kumar","email":"ravi@example.com","company":"Kumar Hosting","tags":"vip,hosting"}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"message":"Contact successfully saved."}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts"],"note":null},"x-errors":[{"status":400,"error":"Phone number is required.","when":"phone is missing."},{"status":400,"error":"too short","when":"The phone number is not valid. The reason is one of empty, no digits, too short, too long or not a mobile."}],"x-plan":"growth"}},"/api/contacts/{phone}/name":{"put":{"operationId":"updateContactName","tags":["Contacts"],"summary":"Set a contact’s name","description":"Sets the contact’s display name. Send version (an integer, from an earlier response) to make the write conditional: if someone else changed the contact meanwhile the call answers 412 version_conflict with the current value instead of overwriting it, and a successful versioned write returns the new version and an ETag.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Name."},"version":{"type":"integer","minimum":1,"description":"Optional. The version you last saw, for a conditional write."}}},"example":{"name":"Ravi Kumar"}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"name":"Ravi Kumar"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts"],"note":null},"x-errors":[{"status":400,"error":"Name is required","when":"name is empty."},{"status":412,"error":"version_conflict","when":"Someone else changed the contact since the version you sent."}],"x-plan":"growth"}},"/api/contacts/{phone}/status":{"put":{"operationId":"updateContactStatus","tags":["Contacts"],"summary":"Set a contact’s status","description":"Moves the contact to a CRM pipeline status. Send version (an integer, from an earlier response) to make the write conditional: if someone else changed the contact meanwhile the call answers 412 version_conflict with the current value instead of overwriting it, and a successful versioned write returns the new version and an ETag.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"Status."},"version":{"type":"integer","minimum":1,"description":"Optional. The version you last saw, for a conditional write."}}},"example":{"status":"Qualified"}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"status":"Qualified"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts"],"note":null},"x-errors":[{"status":400,"error":"Status is required","when":"status is empty."},{"status":412,"error":"version_conflict","when":"Someone else changed the contact since the version you sent."}],"x-plan":"growth"}},"/api/contacts/{phone}/email":{"put":{"operationId":"updateContactEmail","tags":["Contacts"],"summary":"Set a contact’s email","description":"Sets the email address. An empty value clears it. Send version (an integer, from an earlier response) to make the write conditional: if someone else changed the contact meanwhile the call answers 412 version_conflict with the current value instead of overwriting it, and a successful versioned write returns the new version and an ETag.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"Email."},"version":{"type":"integer","minimum":1,"description":"Optional. The version you last saw, for a conditional write."}}},"example":{"email":"ravi@example.com"}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"email":"ravi@example.com"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts"],"note":null},"x-errors":[{"status":412,"error":"version_conflict","when":"Someone else changed the contact since the version you sent."}],"x-plan":"growth"}},"/api/contacts/{phone}/company":{"put":{"operationId":"updateContactCompany","tags":["Contacts"],"summary":"Set a contact’s company","description":"Sets the company. An empty value clears it. Send version (an integer, from an earlier response) to make the write conditional: if someone else changed the contact meanwhile the call answers 412 version_conflict with the current value instead of overwriting it, and a successful versioned write returns the new version and an ETag.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"company":{"type":"string","description":"Company."},"version":{"type":"integer","minimum":1,"description":"Optional. The version you last saw, for a conditional write."}}},"example":{"company":"Kumar Hosting"}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"company":"Kumar Hosting"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts"],"note":null},"x-errors":[{"status":412,"error":"version_conflict","when":"Someone else changed the contact since the version you sent."}],"x-plan":"growth"}},"/api/contacts/{phone}/tags":{"put":{"operationId":"updateContactTags","tags":["Contacts"],"summary":"Set a contact’s tags","description":"Replaces the contact’s tags with this comma-separated list. Newly added tags fire the Tag Added automation trigger. Send version (an integer, from an earlier response) to make the write conditional: if someone else changed the contact meanwhile the call answers 412 version_conflict with the current value instead of overwriting it, and a successful versioned write returns the new version and an ETag.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"string","description":"Tags."},"version":{"type":"integer","minimum":1,"description":"Optional. The version you last saw, for a conditional write."}}},"example":{"tags":"vip,hosting"}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"tags":"vip,hosting"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts"],"note":null},"x-errors":[{"status":412,"error":"version_conflict","when":"Someone else changed the contact since the version you sent."}],"x-plan":"growth"}},"/api/contacts/{phone}/notes":{"put":{"operationId":"updateContactNotes","tags":["Contacts"],"summary":"Set a contact’s notes","description":"Sets the internal notes. An empty value clears them. Send version (an integer, from an earlier response) to make the write conditional: if someone else changed the contact meanwhile the call answers 412 version_conflict with the current value instead of overwriting it, and a successful versioned write returns the new version and an ETag.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"string","description":"Notes."},"version":{"type":"integer","minimum":1,"description":"Optional. The version you last saw, for a conditional write."}}},"example":{"notes":"Asked for a callback on Monday."}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"notes":"Asked for a callback on Monday."}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts"],"note":null},"x-errors":[{"status":412,"error":"version_conflict","when":"Someone else changed the contact since the version you sent."}],"x-plan":"growth"}},"/api/contacts/{phone}/attributes":{"get":{"operationId":"getContactAttributes","tags":["Contacts"],"summary":"Get custom attributes","description":"The contact’s custom attributes: the free-form fields you store beyond name, email, company and tags.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"}],"responses":{"200":{"description":"The attributes object.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"attributes":{"plan":"Business","renewal":"2026-12-01"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts","contacts:read"],"note":null},"x-errors":[],"x-plan":"scale"},"put":{"operationId":"setContactAttributes","tags":["Contacts"],"summary":"Merge custom attributes","description":"Merges the given attributes into the contact’s existing ones. Keys you leave out are kept; use Delete a custom attribute to remove one.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["attributes"],"properties":{"attributes":{"type":"object","description":"Key and value pairs to merge in."}}},"example":{"attributes":{"plan":"Business","renewal":"2026-12-01"}}}}},"responses":{"200":{"description":"The merged attributes.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"attributes":{"plan":"Business","renewal":"2026-12-01"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts"],"note":null},"x-errors":[{"status":400,"error":"attributes object is required","when":"attributes is missing or not an object."}],"x-plan":"growth"}},"/api/contacts/{phone}/attributes/{key}":{"delete":{"operationId":"deleteContactAttribute","tags":["Contacts"],"summary":"Delete a custom attribute","description":"Removes one custom attribute from the contact and returns what is left.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"},{"name":"key","in":"path","required":true,"description":"The attribute name to remove.","schema":{"type":"string"},"example":"renewal"}],"responses":{"200":{"description":"The remaining attributes.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"attributes":{"plan":"Business"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts"],"note":null},"x-errors":[],"x-plan":"growth"}},"/api/contacts/{phone}/opt-out":{"post":{"operationId":"optOutContact","tags":["Contacts"],"summary":"Mark a contact opted out","description":"Marks the contact as opted out of broadcasts and records a consent event. Broadcasts skip opted-out numbers.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"}],"responses":{"200":{"description":"Recorded.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts"],"note":null},"x-errors":[],"x-plan":"growth"}},"/api/contacts/{phone}/opt-in":{"post":{"operationId":"optInContact","tags":["Contacts"],"summary":"Re-subscribe a contact","description":"Clears the opted-out flag and records a consent event. Only do this when the contact has actually asked to hear from you again.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"}],"responses":{"200":{"description":"Recorded.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts"],"note":null},"x-errors":[],"x-plan":"growth"}},"/api/contacts/opted-out":{"get":{"operationId":"listOptedOut","tags":["Contacts"],"summary":"List opted-out contacts","description":"Every contact currently opted out of broadcasts, most recently changed first.","responses":{"200":{"description":"Opted-out contacts.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"data":[{"phone":"919812345678","name":"Asha Rao","tags":"newsletter","updated_at":"2026-09-30T11:02:00.000Z"}],"count":1}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts","contacts:read"],"note":null},"x-errors":[],"x-plan":"scale"}},"/api/contacts/{phone}/read":{"post":{"operationId":"markRead","tags":["Contacts"],"summary":"Mark a chat read","description":"Marks every unread customer message in the chat as read. A dashboard session also records that the signed-in team member opened the chat (seen-by). A key marking a chat read is an integration, so nothing is recorded as a person looking.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"}],"responses":{"200":{"description":"Marked.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts"],"note":null},"x-errors":[],"x-plan":"scale"}},"/api/contacts/{phone}":{"delete":{"operationId":"deleteContact","tags":["Contacts"],"summary":"Delete a contact","description":"Permanently deletes the contact and its chat data. This cannot be undone. A caller limited to assigned chats can only delete contacts assigned to them.","parameters":[{"name":"phone","in":"path","required":true,"description":"Contact phone number with the country code, digits only. A leading + is accepted. A 10-digit number with no country code is treated as Indian (91 is added).","schema":{"type":"string"},"example":"919876543210"}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"deleted":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts"],"note":null},"x-errors":[{"status":404,"error":"Contact not found","when":"No such contact, or not one the caller can see."}],"x-plan":"scale"}},"/api/contacts/bulk-delete":{"post":{"operationId":"bulkDeleteContacts","tags":["Contacts"],"summary":"Delete many contacts","description":"Deletes up to 500 contacts in one call. Contacts the caller cannot see are left alone and are not counted in deleted.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phones"],"properties":{"phones":{"type":"array","items":{"type":"string"},"description":"Phone numbers, 1 to 500."}}},"example":{"phones":["919876543210","919812345678"]}}}},"responses":{"200":{"description":"How many were deleted out of how many were asked for.","content":{"application/json":{"schema":{"type":"object"},"example":{"success":true,"deleted":2,"total":2}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["contacts"],"note":null},"x-errors":[{"status":400,"error":"phones array required","when":"phones is missing or empty."},{"status":400,"error":"max 500 phones per call","when":"More than 500 numbers."}],"x-plan":"scale"}},"/api/team/v2/roster":{"get":{"operationId":"teamRoster","tags":["Team"],"summary":"Team roster","description":"Active team members with working hours, capacity and skills.","responses":{"200":{"description":"The roster.","content":{"application/json":{"schema":{"type":"object"},"example":{"members":[{"user_id":41,"display_name":"Anita","role":"agent"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["team_management","team_management:read"],"note":null},"x-errors":[],"x-plan":"scale"}},"/api/team/v2/performance":{"get":{"operationId":"teamPerformance","tags":["Team"],"summary":"Team performance","description":"Per team member: messages sent, chats replied to, active chats and pickup time over a range.","parameters":[{"name":"range","in":"query","description":"Window, for example 24h, 7d, 30d. Default 7d.","required":false,"schema":{"type":"string","default":"7d"}}],"responses":{"200":{"description":"Per-member figures.","content":{"application/json":{"schema":{"type":"object"},"example":{"range":"7d","members":[]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["team_management","team_management:read"],"note":null},"x-errors":[],"x-plan":"scale"}},"/api/team/v2/sla/dashboard":{"get":{"operationId":"slaDashboard","tags":["Team"],"summary":"SLA dashboard","description":"Live breach count, period totals, compliance percentage, a per-member leaderboard and recent breaches.","parameters":[{"name":"range","in":"query","description":"Window. Default 7d.","required":false,"schema":{"type":"string","default":"7d"}}],"responses":{"200":{"description":"SLA figures.","content":{"application/json":{"schema":{"type":"object"},"example":{"range":"7d"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["team_management","team_management:read"],"note":null},"x-errors":[],"x-plan":"scale"}},"/api/team/v2/handoff":{"get":{"operationId":"handoffQueue","tags":["Team"],"summary":"Hand-off queue","description":"Chats waiting for a person. Claim, release and transfer are POST /api/team/v2/handoff/{phone}/claim, /release and /transfer.","responses":{"200":{"description":"The queue.","content":{"application/json":{"schema":{"type":"object"},"example":{"queue":[]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]}],"x-required-access":{"any":["inbox","team_management","inbox:read","team_management:read"],"note":null},"x-errors":[],"x-plan":"scale"}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"iwk_ API key","description":"Create a key on the API Keys page in the sidebar, then send Authorization: Bearer iwk_..."}},"responses":{"Unauthorized":{"description":"No credential, or an invalid or revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key is expired, read-only for this method, lacks the scope, or the workspace has no API access. See the error code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"No such item in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"BadRequest":{"description":"A parameter is missing or malformed. The error code names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Over the per-key rate limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"integer"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string","description":"Stable machine-readable code."},"message":{"type":"string","description":"Human-readable explanation."}},"required":["error","message"]},"MemberRef":{"type":"object","properties":{"user_id":{"type":"integer","description":"Team member user id."},"name":{"type":"string","description":"Display name.","nullable":true}},"required":["user_id","name"]},"Window":{"type":"object","properties":{"from":{"type":"string","format":"date-time","description":"Start of the window."},"to":{"type":"string","format":"date-time","description":"End of the window."}},"required":["from","to"]},"Receipt":{"type":"object","properties":{"status":{"type":"string","enum":["sent","delivered","read","failed"]},"at":{"type":"string","format":"date-time","description":"When WhatsApp reported it."},"error":{"type":"string","description":"Failure reason."}},"required":["status","at"]},"ConversationSummary":{"type":"object","properties":{"phone":{"type":"string","description":"Contact phone number, digits only, as stored."},"contact_name":{"type":"string","description":"Contact name if known.","nullable":true},"state":{"type":"string","enum":["AI_ONLY","HANDOFF_PENDING","HUMAN_ACTIVE"],"description":"AI_ONLY: the bot is handling it. HANDOFF_PENDING: waiting for a person. HUMAN_ACTIVE: a person has it."},"handover_reason":{"type":"string","description":"Why it was handed to a person.","nullable":true},"assigned_to":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/MemberRef"}]},"claimed_by":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/MemberRef"}]},"unread_count":{"type":"integer","description":"Customer messages nobody has opened, inside the window."},"needs_reply":{"type":"boolean","description":"The customer spoke last (or, when the workspace has the unattended-AI-reply setting, the AI did) and no delivered reply has followed."},"last_message":{"type":"object","properties":{"message_id":{"type":"string","description":"WhatsApp message id.","nullable":true},"sender_type":{"type":"string","enum":["customer","bot","agent","system"],"description":"customer: the contact. bot: the AI agent. agent: a team member (recorded on messages sent from InboxWA). system: any other outbound message (automation, campaign, template, or an older message with no recorded author). A bot reply is never reported as a human.","nullable":true},"sent_at":{"type":"string","format":"date-time","description":"When it was sent."},"preview":{"type":"string","description":"First 140 characters.","nullable":true}},"required":["message_id","sender_type","sent_at","preview"],"nullable":true},"waiting_since":{"type":"string","format":"date-time","description":"When the customer began waiting. Null unless needs_reply.","nullable":true},"waiting_seconds":{"type":"integer","description":"Seconds the customer has been waiting.","nullable":true},"sla_deadline_at":{"type":"string","format":"date-time","description":"SLA response deadline, if a policy applies.","nullable":true},"sla_breached":{"type":"boolean","description":"Deadline passed while still waiting for a person."},"labels":{"type":"array","items":{"type":"string","description":"Label."}},"counts":{"type":"object","properties":{"messages":{"type":"integer"},"from_customer":{"type":"integer"},"from_bot":{"type":"integer"}},"required":["messages","from_customer","from_bot"]}},"required":["phone","contact_name","state","handover_reason","assigned_to","claimed_by","unread_count","needs_reply","last_message","waiting_since","waiting_seconds","sla_deadline_at","sla_breached","labels","counts"]},"ConversationDetail":{"allOf":[{"$ref":"#/components/schemas/ConversationSummary"},{"type":"object","properties":{"handover_at":{"type":"string","description":"When it was handed to a person.","nullable":true},"claimed_at":{"type":"string","description":"When it was claimed.","nullable":true},"resolved_at":{"type":"string","description":"When it was last resolved.","nullable":true},"contact":{"type":"object","properties":{"name":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"company":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"tags":{"type":"array","items":{"type":"string"}}},"required":["name","email","company","status","tags"],"nullable":true}},"required":["handover_at","claimed_at","resolved_at","contact"]}]},"ConversationList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ConversationSummary"}},"next_cursor":{"type":"string","description":"Pass as cursor to get the next page. Null on the last page.","nullable":true},"window":{"$ref":"#/components/schemas/Window"}},"required":["data","next_cursor","window"]},"UnansweredList":{"allOf":[{"$ref":"#/components/schemas/ConversationList"},{"type":"object","properties":{"older_than_seconds":{"type":"integer","description":"The threshold applied."}},"required":["older_than_seconds"]}]},"UnreadSummary":{"type":"object","properties":{"window":{"$ref":"#/components/schemas/Window"},"unread_chats":{"type":"integer"},"unread_messages":{"type":"integer"},"oldest_unread_at":{"type":"string","format":"date-time","description":"Oldest unread customer message.","nullable":true},"waiting_chats":{"type":"integer","description":"Chats where the customer is waiting for a reply."},"oldest_waiting_since":{"type":"string","format":"date-time","description":"When the longest-waiting customer began waiting.","nullable":true},"longest_wait_seconds":{"type":"integer","nullable":true},"by_assignee":{"type":"array","items":{"type":"object","properties":{"assigned_to":{"$ref":"#/components/schemas/MemberRef"},"unread_chats":{"type":"integer"},"unread_messages":{"type":"integer"},"oldest_unread_at":{"type":"string","format":"date-time","nullable":true},"waiting_chats":{"type":"integer"},"oldest_waiting_since":{"type":"string","format":"date-time","nullable":true}},"required":["assigned_to","unread_chats","unread_messages","oldest_unread_at","waiting_chats","oldest_waiting_since"]}},"unassigned":{"type":"object","properties":{"assigned_to":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/MemberRef"}]},"unread_chats":{"type":"integer"},"unread_messages":{"type":"integer"},"oldest_unread_at":{"type":"string","format":"date-time","nullable":true},"waiting_chats":{"type":"integer"},"oldest_waiting_since":{"type":"string","format":"date-time","nullable":true}},"required":["assigned_to","unread_chats","unread_messages","oldest_unread_at","waiting_chats","oldest_waiting_since"]}},"required":["window","unread_chats","unread_messages","oldest_unread_at","waiting_chats","oldest_waiting_since","longest_wait_seconds","by_assignee","unassigned"]},"Message":{"type":"object","properties":{"id":{"type":"integer","description":"Internal row id (used by cursors)."},"message_id":{"type":"string","description":"WhatsApp message id (wamid).","nullable":true},"phone":{"type":"string","description":"Contact phone number."},"sent_at":{"type":"string","format":"date-time","description":"When the message was sent or received."},"sender_type":{"type":"string","enum":["customer","bot","agent","system"],"description":"customer: the contact. bot: the AI agent. agent: a team member (recorded on messages sent from InboxWA). system: any other outbound message (automation, campaign, template, or an older message with no recorded author). A bot reply is never reported as a human."},"actor":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/MemberRef"}]},"sender_label":{"type":"string","description":"Display label recorded at send time.","nullable":true},"type":{"type":"string","description":"text, template, image, audio, document and so on.","nullable":true},"text":{"type":"string","description":"Message text.","nullable":true},"template_name":{"type":"string","description":"Template name when a template was sent.","nullable":true},"media_url":{"type":"string","description":"Media location, if any.","nullable":true},"status":{"type":"string","description":"Inbound: unread or read. Outbound: sent, delivered, read or failed.","nullable":true},"source":{"type":"string","description":"Channel, normally whatsapp.","nullable":true},"receipts":{"type":"array","items":{"$ref":"#/components/schemas/Receipt"},"description":"Only with expand=receipts, and only on outbound messages."}},"required":["sent_at","sender_type"]},"MessageList":{"type":"object","properties":{"phone":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Message"}},"next_cursor":{"type":"string","nullable":true}},"required":["phone","data","next_cursor"]},"MessageDetail":{"allOf":[{"$ref":"#/components/schemas/Message"},{"type":"object","properties":{"error":{"type":"string","description":"Failure reason recorded at send time.","nullable":true}},"required":["error"]}]},"MessageReceipts":{"type":"object","properties":{"message_id":{"type":"string"},"phone":{"type":"string","nullable":true},"current_status":{"type":"string","nullable":true},"receipts":{"type":"array","items":{"$ref":"#/components/schemas/Receipt"}},"recorded_from":{"type":"string","format":"date-time","description":"When receipts began being recorded for this message. Earlier chats keep only the latest status.","nullable":true}},"required":["message_id","phone","current_status","receipts","recorded_from"]},"ConversationReceipts":{"type":"object","properties":{"phone":{"type":"string"},"data":{"type":"array","items":{"type":"object","properties":{"message_id":{"type":"string"},"sent_at":{"type":"string","format":"date-time"},"sender_type":{"type":"string","enum":["customer","bot","agent","system"],"description":"customer: the contact. bot: the AI agent. agent: a team member (recorded on messages sent from InboxWA). system: any other outbound message (automation, campaign, template, or an older message with no recorded author). A bot reply is never reported as a human."},"current_status":{"type":"string","nullable":true},"receipts":{"type":"array","items":{"$ref":"#/components/schemas/Receipt"}}},"required":["message_id","sent_at","sender_type","current_status","receipts"]}}},"required":["phone","data"]},"FailedMessageList":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"message_id":{"type":"string","nullable":true},"phone":{"type":"string"},"sent_at":{"type":"string","format":"date-time"},"sender_type":{"type":"string","enum":["customer","bot","agent","system"],"description":"customer: the contact. bot: the AI agent. agent: a team member (recorded on messages sent from InboxWA). system: any other outbound message (automation, campaign, template, or an older message with no recorded author). A bot reply is never reported as a human."},"actor":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/MemberRef"}]},"sender_label":{"type":"string","nullable":true},"type":{"type":"string","nullable":true},"template_name":{"type":"string","nullable":true},"media_url":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"source":{"type":"string","nullable":true},"error":{"type":"string","description":"Error text recorded at send time.","nullable":true}},"required":["sent_at","sender_type"]}},"next_cursor":{"type":"string","nullable":true},"window":{"$ref":"#/components/schemas/Window"}},"required":["data","next_cursor","window"]},"SeenBy":{"type":"object","properties":{"phone":{"type":"string"},"last_customer_message_at":{"type":"string","format":"date-time","nullable":true},"seen_by_anyone":{"type":"boolean","description":"Some team member opened the chat after the last customer message."},"seen_by":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"integer"},"name":{"type":"string","nullable":true},"first_opened_at":{"type":"string","format":"date-time"},"last_opened_at":{"type":"string","format":"date-time"},"opens":{"type":"integer","description":"Number of recorded opens."},"unseen_customer_messages":{"type":"integer","description":"Customer messages that arrived after this member last opened it."}},"required":["user_id","name","first_opened_at","last_opened_at","opens","unseen_customer_messages"]}},"recorded_from":{"type":"string","format":"date-time","description":"When the workspace began recording opens. Nothing earlier can be reported.","nullable":true}},"required":["phone","last_customer_message_at","seen_by_anyone","seen_by","recorded_from"]},"Views":{"type":"object","properties":{"phone":{"type":"string"},"data":{"type":"array","items":{"type":"object","properties":{"user":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/MemberRef"}]},"opened_at":{"type":"string","format":"date-time"},"marked_read":{"type":"integer","description":"Unread customer messages this open cleared."}},"required":["user","opened_at","marked_read"]}},"next_cursor":{"type":"string","nullable":true}},"required":["phone","data","next_cursor"]},"Unseen":{"type":"object","properties":{"user":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/MemberRef"}]},"window":{"$ref":"#/components/schemas/Window"},"data":{"type":"array","items":{"type":"object","properties":{"phone":{"type":"string"},"contact_name":{"type":"string","nullable":true},"unseen_customer_messages":{"type":"integer"},"last_customer_message_at":{"type":"string","format":"date-time","nullable":true},"last_opened_at":{"type":"string","format":"date-time","nullable":true},"ever_opened_by_user":{"type":"boolean"}},"required":["phone","contact_name","unseen_customer_messages","last_customer_message_at","last_opened_at","ever_opened_by_user"]}}},"required":["user","window","data"]},"ConversationReplies":{"type":"object","properties":{"phone":{"type":"string"},"first_customer_message_at":{"type":"string","format":"date-time","nullable":true},"replies":{"type":"array","items":{"type":"object","properties":{"message_id":{"type":"string","nullable":true},"sent_at":{"type":"string","format":"date-time"},"sender_type":{"type":"string","enum":["customer","bot","agent","system"],"description":"customer: the contact. bot: the AI agent. agent: a team member (recorded on messages sent from InboxWA). system: any other outbound message (automation, campaign, template, or an older message with no recorded author). A bot reply is never reported as a human."},"actor":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/MemberRef"}]},"sender_label":{"type":"string","nullable":true},"answers_message_id":{"type":"string","description":"The customer message this reply answered.","nullable":true},"status":{"type":"string","nullable":true},"response_seconds":{"type":"integer","description":"Bot and system replies: seconds since the customer began waiting. Agent replies: seconds since the customer began waiting for a person. Null when nobody was waiting, or the send failed.","nullable":true}},"required":["message_id","sent_at","sender_type","actor","sender_label","answers_message_id","status","response_seconds"]}},"first_response":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/ReplyBrief"}]},"first_human_response":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/ReplyBrief"}]}},"required":["phone","first_customer_message_at","replies","first_response","first_human_response"]},"ReplyBrief":{"type":"object","properties":{"message_id":{"type":"string","nullable":true},"sender_type":{"type":"string","enum":["customer","bot","agent","system"],"description":"customer: the contact. bot: the AI agent. agent: a team member (recorded on messages sent from InboxWA). system: any other outbound message (automation, campaign, template, or an older message with no recorded author). A bot reply is never reported as a human."},"actor":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/MemberRef"}]},"response_seconds":{"type":"integer"}},"required":["message_id","sender_type","actor","response_seconds"]},"ReplyList":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"message_id":{"type":"string","nullable":true},"phone":{"type":"string"},"sent_at":{"type":"string","format":"date-time"},"sender_type":{"type":"string","enum":["customer","bot","agent","system"],"description":"customer: the contact. bot: the AI agent. agent: a team member (recorded on messages sent from InboxWA). system: any other outbound message (automation, campaign, template, or an older message with no recorded author). A bot reply is never reported as a human."},"actor":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/MemberRef"}]},"sender_label":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"response_seconds":{"type":"integer","nullable":true}},"required":["message_id","phone","sent_at","sender_type","actor","sender_label","status","response_seconds"]}},"next_cursor":{"type":"string","nullable":true},"window":{"$ref":"#/components/schemas/Window"}},"required":["data","next_cursor","window"]},"FirstResponseMetrics":{"type":"object","properties":{"window":{"$ref":"#/components/schemas/Window"},"time_zone":{"type":"string","description":"Time zone used for day grouping."},"episodes":{"type":"integer","description":"Waits that began in the window: the first customer message in a chat, or the first after any outbound message."},"unanswered_episodes":{"type":"integer","description":"Episodes with no delivered reply."},"first_response":{"type":"object","properties":{"count":{"type":"integer","description":"Number of episodes measured."},"median_seconds":{"type":"integer","description":"Median, seconds.","nullable":true},"p90_seconds":{"type":"integer","description":"90th percentile, seconds.","nullable":true},"mean_seconds":{"type":"integer","description":"Mean, seconds.","nullable":true}},"required":["count","median_seconds","p90_seconds","mean_seconds"]},"first_human_response":{"type":"object","properties":{"count":{"type":"integer","description":"Number of episodes measured."},"median_seconds":{"type":"integer","description":"Median, seconds.","nullable":true},"p90_seconds":{"type":"integer","description":"90th percentile, seconds.","nullable":true},"mean_seconds":{"type":"integer","description":"Mean, seconds.","nullable":true}},"required":["count","median_seconds","p90_seconds","mean_seconds"]},"truncated":{"type":"boolean","description":"True when more than 100,000 episodes were in the window; narrow it."},"groups":{"type":"array","items":{"type":"object","properties":{"day":{"type":"string","description":"YYYY-MM-DD in time_zone (group_by=day)."},"episodes":{"type":"integer"},"agent":{"$ref":"#/components/schemas/MemberRef"},"first_response":{"type":"object","properties":{"count":{"type":"integer","description":"Number of episodes measured."},"median_seconds":{"type":"integer","description":"Median, seconds.","nullable":true},"p90_seconds":{"type":"integer","description":"90th percentile, seconds.","nullable":true},"mean_seconds":{"type":"integer","description":"Mean, seconds.","nullable":true}},"required":["count","median_seconds","p90_seconds","mean_seconds"]},"first_human_response":{"type":"object","properties":{"count":{"type":"integer","description":"Number of episodes measured."},"median_seconds":{"type":"integer","description":"Median, seconds.","nullable":true},"p90_seconds":{"type":"integer","description":"90th percentile, seconds.","nullable":true},"mean_seconds":{"type":"integer","description":"Mean, seconds.","nullable":true}},"required":["count","median_seconds","p90_seconds","mean_seconds"]}},"required":["first_human_response"]},"description":"Present when group_by is day or agent."}},"required":["window","time_zone","episodes","unanswered_episodes","first_response","first_human_response","truncated"]},"AssignmentHistory":{"type":"object","properties":{"phone":{"type":"string"},"data":{"type":"array","items":{"type":"object","properties":{"event":{"type":"string","enum":["claimed","released","transferred","resolved"]},"at":{"type":"string","format":"date-time"},"from":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/MemberRef"}]},"to":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/MemberRef"}]},"by":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/MemberRef"}]},"reason":{"type":"string","nullable":true},"source":{"type":"string","enum":["events","audit"],"description":"events: recorded exactly. audit: reconstructed from the earlier team audit log (claims, releases, transfers only)."}},"required":["event","at","from","to","by","reason","source"]}},"detailed_since":{"type":"string","format":"date-time","description":"From here on, history is exact; before it, only the audit log is available.","nullable":true}},"required":["phone","data","detailed_since"]},"Whoami":{"type":"object","properties":{"workspace_id":{"type":"integer"},"auth":{"type":"string","enum":["api_key","session"]},"key":{"type":"object","properties":{"id":{"type":"integer","nullable":true},"scopes":{"type":"array","items":{"type":"string"}},"read_only":{"type":"boolean"}},"required":["id","scopes","read_only"],"nullable":true},"api_access":{"type":"boolean","description":"True means the full API (Scale plan). False means the core API only, or none."},"api_tier":{"type":"string","enum":["none","core","full"],"description":"What the plan includes: none (Starter), core (Growth), full (Scale)."},"reporting_api":{"type":"string","description":"Reporting API version."}},"required":["workspace_id","auth","key","api_access","api_tier","reporting_api"]},"ApiKey":{"type":"object","properties":{"id":{"type":"integer"},"label":{"type":"string"},"last_4":{"type":"string","description":"Last four characters, for recognising a key."},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":"string","format":"date-time","nullable":true},"revoked_at":{"type":"string","format":"date-time","nullable":true},"scopes":{"type":"array","items":{"type":"string"}},"read_only":{"type":"boolean"},"expires_at":{"type":"string","format":"date-time","nullable":true},"expired":{"type":"boolean"},"rate_limit_per_min":{"type":"integer","description":"Per-key override.","nullable":true},"effective_rate_limit_per_min":{"type":"integer","description":"What actually applies. Null means unlimited.","nullable":true},"rotated_from":{"type":"integer","nullable":true},"created_by_name":{"type":"string","nullable":true},"revoked_by_name":{"type":"string","nullable":true}},"required":["id","label","last_4","created_at","last_used_at","revoked_at","scopes","read_only","expires_at","expired","rate_limit_per_min","effective_rate_limit_per_min","rotated_from","created_by_name","revoked_by_name"]},"ApiKeyList":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}},"defaults":{"type":"object","properties":{"rate_limit_per_min":{"type":"integer"},"max_keys":{"type":"integer"}},"required":["rate_limit_per_min","max_keys"]},"api_access":{"type":"boolean"},"api_tier":{"type":"string","enum":["none","core","full"],"description":"What the plan includes: none (Starter), core (Growth), full (Scale)."}},"required":["keys","defaults","api_access","api_tier"]},"ApiKeyCreated":{"type":"object","properties":{"id":{"type":"integer"},"label":{"type":"string"},"key":{"type":"string","description":"The plaintext key. Shown once and never again."},"last_4":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"read_only":{"type":"boolean"},"expires_at":{"type":"string","format":"date-time","nullable":true},"rate_limit_per_min":{"type":"integer","nullable":true},"created_at":{"type":"string","format":"date-time"},"message":{"type":"string"}},"required":["id","label","key","last_4","scopes","read_only","expires_at","rate_limit_per_min","created_at","message"]},"ApiKeyRotated":{"type":"object","properties":{"id":{"type":"integer"},"label":{"type":"string"},"key":{"type":"string","description":"The new plaintext key. Shown once."},"last_4":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"rotated_from":{"type":"integer"},"old_key_valid_until":{"type":"string","format":"date-time","description":"Set when grace_hours was used.","nullable":true},"message":{"type":"string"}},"required":["id","label","key","last_4","scopes","rotated_from","old_key_valid_until","message"]},"ScopeCatalog":{"type":"object","properties":{"groups":{"type":"object","additionalProperties":{"type":"string"},"description":"Group key to display name."},"scopes":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"group":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"viewOnly":{"type":"boolean","description":"Only ever grants viewing."},"readScope":{"type":"string","description":"The :read variant of this scope, when there is one.","nullable":true}},"required":["key","group","label","description","readScope"]}},"readScopes":{"type":"array","items":{"type":"string"}}},"required":["groups","scopes","readScopes"]},"KeyUsage":{"type":"object","properties":{"range":{"type":"string"},"totals":{"type":"object","properties":{"calls":{"type":"integer"},"errors":{"type":"integer"},"forbidden":{"type":"integer"},"serverErrors":{"type":"integer"}},"required":["calls","errors","forbidden","serverErrors"]},"daily":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"calls":{"type":"integer"},"errors":{"type":"integer"}},"required":["date","calls","errors"]}},"topEndpoints":{"type":"array","items":{"type":"object","properties":{"method":{"type":"string"},"path":{"type":"string"},"calls":{"type":"integer"},"errors":{"type":"integer"}},"required":["method","path","calls","errors"]}},"recent":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"ts":{"type":"string","format":"date-time"},"method":{"type":"string"},"path":{"type":"string"},"status":{"type":"integer"},"ip":{"type":"string","nullable":true}},"required":["id","ts","method","path","status","ip"]}}},"required":["range","totals","daily","topEndpoints","recent"]},"WebhookConfig":{"type":"object","properties":{"configured":{"type":"boolean"},"endpoint_url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"updated_at":{"type":"string","format":"date-time","nullable":true},"updated_by_name":{"type":"string","nullable":true},"valid_events":{"type":"array","items":{"type":"string"}},"api_tier":{"type":"string","enum":["none","core","full"],"description":"What the plan includes: none (Starter), core (Growth), full (Scale)."},"webhooks_available":{"type":"boolean","description":"False on Starter, which has no webhooks."},"event_catalog":{"type":"array","items":{"type":"object","properties":{"event":{"type":"string"},"group":{"type":"string"},"plan":{"type":"string","enum":["any","scale"]},"description":{"type":"string"},"available":{"type":"boolean","description":"False when the event needs a plan this workspace is not on: Starter has none, Growth has the events marked any, Scale has all."}},"required":["event","group","plan","description","available"]}},"credit_alert_levels":{"type":"array","items":{"type":"integer","description":"Percent of the month’s allocation."}}},"required":["configured","endpoint_url","events","enabled","valid_events","api_tier","webhooks_available","event_catalog","credit_alert_levels"]},"WebhookEnvelope":{"type":"object","properties":{"event":{"type":"string","description":"Event name."},"sent_at":{"type":"string","format":"date-time"},"correlation_id":{"type":"string","description":"Unique id, repeated in X-Correlation-Id."},"actor":{"type":"string","description":"Always inboxwa."},"data":{"type":"object","description":"Event payload. See each event."}},"required":["event","sent_at","correlation_id","actor","data"]},"WebhookTestResult":{"type":"object","properties":{"ok":{"type":"boolean"},"status":{"type":"integer","description":"HTTP status your endpoint returned.","nullable":true},"attempts":{"type":"integer"},"error":{"type":"string","nullable":true},"duration_ms":{"type":"integer"},"event":{"type":"string"}},"required":["ok","status","attempts","error","duration_ms","event"]},"Credits":{"additionalProperties":true,"type":"object","properties":{"plan":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"monthly_inr":{"type":"number"},"credits_allocation":{"type":"integer"},"overage_inr_per_credit":{"type":"number"}},"required":["key","label","monthly_inr","credits_allocation","overage_inr_per_credit"]},"cycle":{"type":"object","properties":{"start":{"type":"string","format":"date-time"},"end":{"type":"string","format":"date-time"},"kind":{"type":"string","description":"calendar_month."}},"required":["start","end","kind"]},"used_credits":{"type":"number"},"allocation":{"type":"number","description":"Plan bundle plus active top-ups."},"topup_credits":{"type":"number"},"remaining_credits":{"type":"number"},"overage_credits":{"type":"number"},"overage_inr":{"type":"number"}},"required":["plan","cycle","used_credits","allocation","remaining_credits","overage_credits","overage_inr"]}}},"x-guides":[{"id":"overview","title":"Overview","blocks":[{"t":"p","text":"The InboxWA API is the same surface the web app runs on. Whatever your team can see or do in InboxWA, a key can see or do, under the same permissions. Only the calls documented in this reference are supported for integrations; see \"What is supported\" below."},{"t":"p","text":"Requests and responses are JSON over HTTPS. The base address is `https://inboxwa.online`. Send phone numbers with the country code, digits only; a leading + and spaces are accepted, so `919876543210` and `+91 98765 43210` are the same number. A 10-digit number with no country code is taken to be an Indian number and has 91 put in front of it, so always include the country code for any other country. Timestamps in the Reporting API and in webhooks are ISO 8601 in UTC."},{"t":"ul","items":["**The whole product.** Chats, contacts, leads, messaging, templates, automation, the AI agent, credits, team, SLA, integrations and more are reachable with a key.","**Messaging and contacts.** Send a message or a broadcast, and create and update contacts, their tags, attributes and consent. These are part of the core API, included in Growth and Scale. Reading contacts and chats with a key is part of the full API (Scale plan).","**Plans.** Starter has no API and no webhooks. Growth has the core API and five webhook events. Scale has every call, the Reporting API and all webhook events. See [Plans and API access](#/guide/plans).","**The reporting API.** Purpose-built calls for unread, unanswered, seen-by, receipts, replies and first-response times under `/api/reporting/v1`. Requires the Scale plan.","**Push as well as pull.** Signed webhooks, and an MCP endpoint for your own AI tools."]},{"t":"callout","tone":"note","title":"What is supported","text":"The calls documented in this reference are the supported API. They keep their shape, and any change to one is listed in the changelog. Every other route accepts the same key but is the web app’s internal interface: it is not documented, it is unsupported for integrations, and it can change or disappear without notice. `GET /api/docs/routes` lists those routes for reference only. Do not build an integration on them."},{"t":"callout","tone":"note","title":"For AI assistants and other tools","text":"This documentation is also published without JavaScript: the whole reference as [Markdown](/docs/api.md), the [OpenAPI document](/openapi.json), and an index for AI assistants at [/llms.txt](/llms.txt). Every guide and every call has its own Markdown page too, for example [API keys](/docs/api/guide/keys.md)."}]},{"id":"plans","title":"Plans and API access","blocks":[{"t":"p","text":"What the API includes depends on the plan. A call outside the plan is refused with a clear error, whatever scopes the key has."},{"t":"table","head":["","Starter","Growth","Scale"],"rows":[["API keys","No","Yes","Yes"],["What a key can call","Nothing","The core API","Every call, including reading contacts and chats and the Reporting API"],["Outbound webhooks","No","5 core events","All 11 events"],["Zapier and Make","No","Yes","Yes"]]},{"t":"h","text":"The core API (Growth and Scale)"},{"t":"p","text":"The calls an integration needs: send messages, keep contacts up to date, list templates, and connect Zapier or Make."},{"t":"ul","items":["`POST /api/send-message`: Send a message","`POST /api/bulk-send`: Send a broadcast","`GET /api/templates`: List message templates","`POST /api/contacts`: Create or update a contact","`PUT /api/contacts/:phone/name`: Set a contact’s name","`PUT /api/contacts/:phone/status`: Set a contact’s status","`PUT /api/contacts/:phone/email`: Set a contact’s email","`PUT /api/contacts/:phone/company`: Set a contact’s company","`PUT /api/contacts/:phone/tags`: Set a contact’s tags","`PUT /api/contacts/:phone/notes`: Set a contact’s notes","`PUT /api/contacts/:phone/attributes`: Merge custom attributes","`DELETE /api/contacts/:phone/attributes/:key`: Delete a custom attribute","`POST /api/contacts/:phone/opt-out`: Mark a contact opted out","`POST /api/contacts/:phone/opt-in`: Re-subscribe a contact","`/api/zapier/*` (any method): Zapier triggers and actions","`GET /api/reporting/v1/whoami`: Check a key"]},{"t":"h","text":"The full API (Scale)"},{"t":"p","text":"Every other call a key can make: reading contacts and chats, the Reporting API, deleting contacts, marking a chat read, team, SLA and credits, key and webhook management with a key, and the rest of the app’s routes. Only the calls documented in this reference are supported; see the Overview."},{"t":"h","text":"Webhook events by plan"},{"t":"ul","items":["**Growth and Scale:** `message_received`, `message_sent`, `contact_created`, `handoff`, `tag_added`.","**Scale adds:** `message_status`, `conversation_seen`, `conversation_assigned`, `conversation_resolved`, `sla_breached`, `credits_threshold`.","**Starter:** none. A webhook set up while on a higher plan stops receiving events when the plan drops."]},{"t":"h","text":"When a call is outside the plan"},{"t":"table","head":["Status","error","When"],"rows":[["403","`api_not_in_plan`","A Starter workspace used a key, or tried to create one."],["403","`api_access_not_enabled`","A Growth key made a call that is part of the full API."],["403","`webhooks_not_in_plan`","A Starter workspace tried to set up webhooks."],["403","`event_requires_scale`","A Growth workspace subscribed to an event that is part of Scale."]]},{"t":"p","text":"`GET /api/reporting/v1/whoami` shows what your plan includes: `api_tier` is `core` on Growth and `full` on Scale."}]},{"id":"quickstart","title":"Quickstart","blocks":[{"t":"p","text":"Three steps from nothing to a sent message. The API is included in the Growth and Scale plans."},{"t":"ul","items":["**Create a key.** Open **API Keys** in the sidebar and choose Create key. To send messages the key needs the `whatsapp_api` scope (or full access), and the workspace must have the WhatsApp Cloud API connected. For reporting, choose the read-only scopes. Copy the key now: it is shown once. [How keys work](#/guide/keys).","**Check it.** `GET /api/reporting/v1/whoami` returns the workspace, the scopes and what your plan includes (`api_tier`). It is part of the core API, so it works on Growth and Scale, and it tells you why another call is refused.","**Send a message.** `POST /api/send-message` with a template, or with text inside the 24-hour window. It needs the `whatsapp_api` scope."]},{"t":"tabs","title":"Check the key, then send a message","tabs":[{"label":"curl","lang":"bash","code":"# 1. Check the key\ncurl https://inboxwa.online/api/reporting/v1/whoami \\\n  -H \"Authorization: Bearer iwk_yourkeyhere\"\n\n# 2. Send a template message\ncurl -X POST https://inboxwa.online/api/send-message \\\n  -H \"Authorization: Bearer iwk_yourkeyhere\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"to\":\"919876543210\",\"type\":\"template\",\"template\":{\"name\":\"order_confirmation\",\"language\":\"en_US\",\"parameters\":[\"ORD-4271\",\"2026-10-05\"]}}'"},{"label":"Node.js","lang":"javascript","code":"const headers = {\n  Authorization: \"Bearer \" + process.env.INBOXWA_KEY,\n  \"Content-Type\": \"application/json\",\n};\n\nconst who = await fetch(\"https://inboxwa.online/api/reporting/v1/whoami\", { headers });\nconsole.log(await who.json());\n\nconst res = await fetch(\"https://inboxwa.online/api/send-message\", {\n  method: \"POST\",\n  headers,\n  body: JSON.stringify({\n    to: \"919876543210\",\n    type: \"template\",\n    template: { name: \"order_confirmation\", language: \"en_US\", parameters: [\"ORD-4271\", \"2026-10-05\"] },\n  }),\n});\nconst out = await res.json();\nif (!res.ok) throw new Error(out.error);\nconsole.log(out.messageId);"},{"label":"Python","lang":"python","code":"import os, requests\n\nheaders = {\"Authorization\": \"Bearer \" + os.environ[\"INBOXWA_KEY\"]}\nprint(requests.get(\"https://inboxwa.online/api/reporting/v1/whoami\", headers=headers, timeout=30).json())\n\nr = requests.post(\n    \"https://inboxwa.online/api/send-message\",\n    headers=headers,\n    json={\n        \"to\": \"919876543210\",\n        \"type\": \"template\",\n        \"template\": {\"name\": \"order_confirmation\", \"language\": \"en_US\", \"parameters\": [\"ORD-4271\", \"2026-10-05\"]},\n    },\n    timeout=30,\n)\nr.raise_for_status()\nprint(r.json()[\"messageId\"])"},{"label":"PHP","lang":"php","code":"function inboxwa(string $method, string $path, ?array $body = null): array {\n  $ch = curl_init(\"https://inboxwa.online\" . $path);\n  curl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => $method,\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\"Authorization: Bearer \" . getenv(\"INBOXWA_KEY\"), \"Content-Type: application/json\"],\n    CURLOPT_POSTFIELDS => $body ? json_encode($body) : null,\n  ]);\n  return json_decode(curl_exec($ch), true);\n}\n\nprint_r(inboxwa(\"GET\", \"/api/reporting/v1/whoami\"));\n$out = inboxwa(\"POST\", \"/api/send-message\", [\n  \"to\" => \"919876543210\",\n  \"type\" => \"template\",\n  \"template\" => [\"name\" => \"order_confirmation\", \"language\" => \"en_US\", \"parameters\" => [\"ORD-4271\", \"2026-10-05\"]],\n]);\necho $out[\"messageId\"];"}]},{"t":"h","text":"Then ask a real question"},{"t":"p","text":"On the Scale plan, list the chats where a customer has been waiting more than 30 minutes."},{"t":"tabs","title":"Unanswered chats","tabs":[{"label":"curl","lang":"bash","code":"curl \"https://inboxwa.online/api/reporting/v1/conversations/unanswered?older_than=30m\" \\\n  -H \"Authorization: Bearer iwk_yourkeyhere\""},{"label":"Node.js","lang":"javascript","code":"const res = await fetch(\n  \"https://inboxwa.online/api/reporting/v1/conversations/unanswered?older_than=30m\",\n  { headers: { Authorization: \"Bearer \" + process.env.INBOXWA_KEY } },\n);\nif (!res.ok) throw new Error((await res.json()).message);\nconst { data } = await res.json();\nfor (const chat of data) {\n  console.log(chat.phone, chat.waiting_seconds + \"s\", chat.assigned_to?.name ?? \"unassigned\");\n}"},{"label":"Python","lang":"python","code":"import os, requests\n\nr = requests.get(\n    \"https://inboxwa.online/api/reporting/v1/conversations/unanswered\",\n    params={\"older_than\": \"30m\"},\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"INBOXWA_KEY\"]},\n    timeout=30,\n)\nr.raise_for_status()\nfor chat in r.json()[\"data\"]:\n    print(chat[\"phone\"], chat[\"waiting_seconds\"], (chat[\"assigned_to\"] or {}).get(\"name\"))"},{"label":"PHP","lang":"php","code":"$ch = curl_init(\"https://inboxwa.online/api/reporting/v1/conversations/unanswered?older_than=30m\");\ncurl_setopt_array($ch, [\n  CURLOPT_RETURNTRANSFER => true,\n  CURLOPT_HTTPHEADER => [\"Authorization: Bearer \" . getenv(\"INBOXWA_KEY\")],\n]);\n$body = json_decode(curl_exec($ch), true);\nforeach ($body[\"data\"] as $chat) {\n  echo $chat[\"phone\"], \" \", $chat[\"waiting_seconds\"], \"s\\n\";\n}"}]}]},{"id":"authentication","title":"Authentication","blocks":[{"t":"p","text":"Send the key as a bearer token on every request: `Authorization: Bearer iwk_...`. Keys are created by a workspace admin on the **API Keys** page in the sidebar. The plaintext is shown once and only its hash is stored, so a lost key is revoked and replaced, never recovered."},{"t":"ul","items":["**Expiry.** A key can be given an expiry of up to 730 days. An expired key is refused with `key_expired`.","**Rotation.** Rotate a key to get a replacement with the same scopes and limit. A grace period of up to 168 hours keeps the old key working while you switch over.","**Audit.** Every call, including refused and rate-limited ones, is logged per key with method, path, status and IP, and can be read for 30 days.","**Plan.** A workspace on Starter has no API: a key is refused with `api_not_in_plan`, and a key cannot be created. On Growth a key may make the core calls only; see [Plans and API access](#/guide/plans).","**Bad credentials.** A request with no credential at all is answered 401. A key that does not exist or has been revoked is answered 403, and so is an expired one (`key_expired`). A 403 is not a reason to retry.","**Full-access keys are powerful.** A full-access key created by an admin can do anything that admin can, including creating, rotating and revoking keys. Give integrations scoped keys."]},{"t":"callout","tone":"warn","title":"Keep keys server-side","text":"A key carries the workspace’s data. Do not embed one in a browser page or a mobile app. Use a read-only key for dashboards and give each integration its own key so one can be revoked alone."}]},{"id":"keys","title":"API keys","blocks":[{"t":"p","text":"Every call is made with an API key, and keys are managed on the **API Keys** page in the sidebar. A workspace admin can create, edit, rotate and revoke keys there. Other members with the settings permission can see the list and each key’s activity. The same key-management calls are in the reference below; they need an admin, either a dashboard session or a full-access key created by an admin. A scoped key is refused with `scope_denied`."},{"t":"h","text":"Create a key"},{"t":"ul","items":["**Name it** after the integration, for example \"Order sync\". The name shows in the key list and in the activity log.","**Choose the access.** Full access carries everything its creator can do. A read-only key can only read. A custom key holds exactly the scopes you choose; see [Scopes and permissions](#/guide/scopes).","**Set an expiry** if you want one, from 1 to 730 days. An expired key is refused with `key_expired`.","**Set a rate limit** if you want one, from 10 to 100,000 requests per minute. Leave it blank for the default; see [Rate limits](#/guide/rate-limits).","**Copy the key now.** It starts with `iwk_` and is shown once. Only a SHA-256 hash is stored, so a lost key is replaced, never recovered."]},{"t":"h","text":"Manage a key"},{"t":"ul","items":["**Edit** to rename a key or change its expiry or limit. Scopes are fixed when a key is created; to change them, create a new key.","**Rotate** to get a replacement with the same name, scopes and limit. A grace period of up to 168 hours keeps the old key working while you switch over, then it stops on its own.","**Revoke** to stop a key at once. This cannot be undone. Create and deploy the replacement first so nothing goes down.","**Activity** shows each key’s calls for the last 7 or 30 days: method, path, status and IP, including refused and rate-limited calls. The log is kept for 30 days.","A workspace can hold up to 20 active keys."]},{"t":"h","text":"Good practice"},{"t":"ul","items":["One key per integration, so any one can be revoked alone.","Scoped keys for integrations. A full-access key can also create and revoke keys, so if one leaks, so can everything it minted.","Read-only keys for dashboards and reports.","An expiry on keys you hand to a contractor or a trial.","Keep keys on your server, in environment variables or a secrets manager. Never put one in a web page, a mobile app or a git repository.","Rotate when someone who knew a key leaves, and check the last-used time on the list: a key nobody uses should not exist."]},{"t":"callout","tone":"note","title":"Which plan does what","text":"Keys are part of Growth and Scale; Starter has none. On Growth a key can make the core calls: sending, creating and updating contacts, listing templates and Zapier. Any other call is refused with `api_access_not_enabled`, whatever the key’s scopes. On Scale a key can call everything. `GET /api/reporting/v1/whoami` shows which applies to yours. See [Plans and API access](#/guide/plans)."}]},{"id":"scopes","title":"Scopes and permissions","blocks":[{"t":"p","text":"A key carries `all`, or any combination of the scopes below. Scopes choose how much of the workspace the key reaches, from one screen to everything."},{"t":"p","text":"A key can never do more than the admin who created it. Its effective access is the creator’s permissions intersected with its scopes."},{"t":"h","text":"Read-only scopes"},{"t":"p","text":"A `:read` scope lets a key call GET endpoints in that area and nothing else. A key made only of read scopes is a read-only key: every write method is refused with `read_only_key`, on every route, including routes that carry no permission check of their own. Use these for dashboards and reports."},{"t":"table","head":["Scope","Read-only variant","What it covers"],"rows":[["`inbox`","`inbox:read`","Read chats and messages; claim, release, transfer, snooze, label and note conversations."],["`contacts`","`contacts:read`","Contacts, leads and deals, tags, attributes, segments, import."],["`campaigns`","`campaigns:read`","Bulk send, scheduled sends, campaign control."],["`automations`","`automations:read`","Workflows, auto-replies, drip sequences and their analytics."],["`templates`","`templates:read`","Message templates and WhatsApp Flows."],["`analytics`","`analytics:read`","Dashboards, engagement and message statistics."],["`whatsapp_api`","","Send through the Meta Cloud API. Layered with templates and campaigns."],["`contacts_delete`","","Hard delete and bulk delete of contacts."],["`contacts_export`","","Bulk export of contacts."],["`campaign_approve`","","Send directly and approve or reject pending bulk sends."],["`templates_delete`","","Delete WhatsApp templates."],["`inbox_assigned_only`","","Restricts inbox reads and sends to chats assigned to the key creator."],["`ai_agent`","`ai_agent:read`","Bot configuration: knowledge base, pricing, behaviour, on and off."],["`ai_kb_edit`","","Change the FAQs the bot answers from."],["`ai_pricing_edit`","","Change the pricing sheets the bot quotes from."],["`ai_behavior_edit`","","Change persona, system prompt and response style."],["`ai_audit_view`","","Read AI usage, cost and handover audit trail."],["`ai_provision`","","Turn the bot on or off for the workspace."],["`team_management`","`team_management:read`","Roster, invites, roles, performance, goals, SLA, audit log."],["`team_remove_member`","","Remove members from the workspace."],["`audit_view`","","Read the team audit log."],["`settings`","`settings:read`","Workspace settings, webhooks, integrations, API keys (list)."],["`billing_view`","","Plan, usage, credits and invoices."],["`billing_manage`","","Change plan, payment methods and top up credits."]]},{"t":"callout","tone":"note","title":"Reporting API access","text":"The reporting calls accept `inbox` or `contacts` (and their `:read` variants) for chat content, and `inbox`, `team_management` or `analytics` (and `:read`) for team accountability data such as who opened a chat and how fast replies were."}]},{"id":"rate-limits","title":"Rate limits","blocks":[{"t":"p","text":"Each key is limited per minute. The default is 600 requests per minute per key, and a key can be given its own limit from 10 to 100,000. Some long-standing integrations are exempt."},{"t":"callout","tone":"note","title":"Sending is not throttled by the default","text":"Sending messages is metered by your credits and by WhatsApp’s own limits, not by request rate. `POST /api/send-message` and `POST /api/bulk-send` are therefore not held to the default limit, and are not counted against it. A limit you set on a key yourself is a deliberate choice and applies to every call, including sends."},{"t":"p","text":"Every response from a limited key carries the current state:"},{"t":"table","head":["Header","Meaning"],"rows":[["`X-RateLimit-Limit`","Requests allowed per minute for this key."],["`X-RateLimit-Remaining`","Requests left in the current window."],["`X-RateLimit-Reset`","Seconds until the window rolls."],["`Retry-After`","On a 429 only: seconds to wait before retrying."]]},{"t":"code","lang":"json","title":"429 Too Many Requests","code":"{\n  \"error\": \"rate_limited\",\n  \"message\": \"This key is limited to 600 requests per minute. Retry after 12s.\",\n  \"retry_after_seconds\": 12\n}"},{"t":"ul","items":["Back off for `Retry-After` seconds, then retry. Do not retry in a tight loop.","Do not poll the same endpoint faster than every 30 seconds. For \"tell me when something happens\", use webhooks.","Prefer one list call with filters over many single-item calls."]},{"t":"h","text":"Two limits, two response shapes"},{"t":"p","text":"A key is held to its own limit, and all API traffic from one IP address is also held to a platform-wide ceiling. Both answer 429, but they answer differently. Whichever you meet, back off for `Retry-After` seconds."},{"t":"table","head":["","Per-key limit","Per-IP ceiling"],"rows":[["Limit","600 a minute by default, or the key’s own","1,000 a minute per IP address"],["Not applied to","Sending (`/api/send-message`, `/api/bulk-send`) on a key with no limit of its own","Sending (`/api/send-message`, `/api/bulk-send`)"],["Body","`{\"error\":\"rate_limited\",\"message\":\"...\",\"retry_after_seconds\":12}`","`{\"error\":\"Too many requests, please try again later.\"}`"],["Headers","`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `Retry-After`","`RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, `RateLimit-Policy`, `Retry-After`"]]},{"t":"p","text":"A per-key limit above 1,000 a minute only helps when calls come from more than one IP address, because the ceiling applies first."}]},{"id":"pagination","title":"Pagination and windows","blocks":[{"t":"p","text":"Lists return `data` and a `next_cursor`. To get the next page, pass `next_cursor` back as `cursor`. Treat it as opaque. A `null` cursor means the last page. `limit` is 1 to 200; each endpoint states its default."},{"t":"p","text":"List endpoints look at a window of time rather than all history, so one call cannot scan years of chats. `since` and `to` accept an ISO 8601 timestamp or a date. Defaults and maximum spans are stated per endpoint: 30 days for chat lists, 7 days for replies, metrics and failed messages. The window is echoed back in the response."},{"t":"code","lang":"bash","title":"Walking every page","code":"cursor=\"\"\nwhile :; do\n  page=$(curl -s \"https://inboxwa.online/api/reporting/v1/conversations?limit=200&unread=true${cursor:+&cursor=$cursor}\" \\\n    -H \"Authorization: Bearer $INBOXWA_KEY\")\n  echo \"$page\" | jq -c '.data[]'\n  cursor=$(echo \"$page\" | jq -r '.next_cursor // empty')\n  [ -z \"$cursor\" ] && break\ndone"}]},{"id":"errors","title":"Errors","blocks":[{"t":"p","text":"Errors are JSON, but two conventions live side by side, because this API is the web app’s own interface. Newer endpoints return a stable snake_case code in `error` and a sentence in `message`. Older endpoints return a sentence in `error` and no `message`. Each table below says which is which."},{"t":"h","text":"Stable codes"},{"t":"p","text":"The Reporting API, key management, webhook settings and the checks every key goes through return a code. Match on `error`."},{"t":"code","lang":"json","title":"Error shape","code":"{\n  \"error\": \"scope_denied\",\n  \"message\": \"This API key isn't scoped for inbox. Mint a new key with the right scope on the API Keys page.\",\n  \"requiredPermission\": \"inbox\",\n  \"keyScopes\": [\"templates\"]\n}"},{"t":"table","head":["Status","error","When"],"rows":[["400","`invalid_*`, `since_after_to`, `window_too_large`","A parameter is missing or malformed. The code names it."],["403","`key_expired`","The key has passed its expiry."],["403","`read_only_key`","A read-only key attempted a write."],["403","`scope_denied`","The key’s scopes do not cover this endpoint, or an admin-only endpoint was called with a scoped key."],["403","`permission_denied`","The admin who created the key lacks the permission."],["403","`admin_required`","An admin-only action by someone who is not an admin."],["403","`api_not_in_plan`","The workspace is on Starter, which has no API."],["403","`api_access_not_enabled`","The call is part of the full API, which is included in the Scale plan, and the key belongs to a Growth workspace."],["403","`webhooks_not_in_plan`","The workspace is on Starter, which has no webhooks."],["403","`event_requires_scale`","A webhook subscription asks for an event that is part of the Scale plan."],["403","`feature_disabled_for_org`","A feature the call needs, such as the WhatsApp Cloud API, is switched off for the workspace."],["403","`account_suspended`, `membership_revoked`","The admin who created the key is suspended or no longer in the workspace."],["404","`not_found`","No such item in this workspace. A chat outside a member’s assignment also returns this."],["412","`version_conflict`","A contact was edited by someone else; refetch and retry."],["429","`rate_limited`","Over the per-key limit. See Retry-After and the Rate limits guide."],["500","`internal_error`","Our side. Retry with backoff; it is safe for reads."]]},{"t":"h","text":"Sentences, on older endpoints"},{"t":"p","text":"The sign-in check, sending, and the contact calls return the error as a sentence in `error`. The text can change, so match on the HTTP status, and for sending also on `errorCode`. Each call’s own page lists its errors."},{"t":"table","head":["Status","Error as returned","When"],"rows":[["401","Unauthorized: No token provided","No credential was sent."],["403","Forbidden: Invalid or revoked API key","The key does not exist or was revoked. An expired key is `key_expired` above."],["403","Account suspended. Contact your admin.","The admin who created the key is suspended."],["400","Phone number is required, Message text is required, Template name is required","A required field of a send call is missing."],["404","Contact not found","No such contact, or one the caller cannot see."],["500","An internal error occurred. Please try again.","Our side, on the contact calls."],["500","The reason WhatsApp gave","WhatsApp refused a send. See the next section."]]},{"t":"h","text":"When a send fails"},{"t":"p","text":"`POST /api/send-message` answers **500** when WhatsApp refuses the message. The reason is in `error`, and WhatsApp’s details are in `errorCode`, `errorTitle`, `errorSubcode` and `errorDetails`. Unlike other 5xx responses this is usually not a fault on our side, and sending the same request again will fail the same way, so read `errorCode` before you retry."},{"t":"table","head":["errorCode","What it means","What to do"],"rows":[["131047","More than 24 hours since the customer last wrote, so free-form text and media are not allowed.","Send a template instead."],["131026","The message could not be delivered to that number, for example it is not on WhatsApp.","Do not retry. Check the number."],["132001","The template does not exist in that language.","Fix the template name or language."],["132000","The number of template parameters does not match the template.","Send the right number of parameters."],["131049","WhatsApp declined to deliver a marketing message to protect the customer experience.","Do not retry straight away."],["130429","WhatsApp’s throughput limit was reached.","Retry with backoff."]]},{"t":"p","text":"These are WhatsApp’s own codes. The full list is in Meta’s Cloud API error reference."}]},{"id":"concepts","title":"How the numbers are defined","blocks":[{"t":"h","text":"Who sent a message"},{"t":"p","text":"Every message has a `sender_type`. `customer` is the contact. `bot` is the AI agent. `agent` is a team member, recorded on messages sent from InboxWA along with their name. `system` is any other outbound message: an automation, a campaign, a template, or an older message with no recorded author. A bot reply is never reported as a human."},{"t":"h","text":"Two clocks for response time"},{"t":"p","text":"A bot usually answers in seconds, so a single response time hides what a team lead wants to know: how long until a person answered. A bot or system reply is timed from when the customer began waiting. An agent reply is timed from when the customer began waiting for a person. `first_human_response` is the figure to hold a team to."},{"t":"callout","tone":"note","title":"A failed send is not a reply","text":"A message that failed to send never reached the customer, so it does not stop a wait, does not count as a first response, and does not make a chat \"replied to\"."},{"t":"h","text":"What is recorded, and from when"},{"t":"ul","items":["**Receipts.** Delivery and read times are recorded from the day recording began (the `recorded_from` field says when). Earlier messages keep only their latest status.","**Read receipts** depend on the customer. WhatsApp sends a read receipt only when the customer has read receipts switched on.","**Seen-by** means a team member opened the chat in the InboxWA inbox or the embedded panel, not that they read it on a phone. A key marking a chat read is an integration and is not recorded as a person looking.","**Assignment history** is exact from `detailed_since`; before that it is rebuilt from the team audit log, which has claims, releases and transfers but not resolutions."]}]},{"id":"webhooks","title":"Webhooks","blocks":[{"t":"p","text":"Register one HTTPS endpoint per workspace and choose the events. Webhooks are part of Growth (five core events) and Scale (all events); Starter has none. Each event is a POST with a JSON body in this envelope:"},{"t":"code","lang":"json","title":"Envelope","code":"{\n  \"event\": \"message_status\",\n  \"sent_at\": \"2026-10-02T09:22:49.000Z\",\n  \"correlation_id\": \"iwa_m1x2y3z4_a1b2c3d4e5f6\",\n  \"actor\": \"inboxwa\",\n  \"data\": { }\n}"},{"t":"table","head":["Header","Value"],"rows":[["`X-Webhook-Event`","The event name."],["`X-Webhook-Timestamp`","Unix seconds when it was sent."],["`X-Correlation-Id`","Same as correlation_id. Unique per event; use it to de-duplicate."],["`X-Webhook-Signature`","`sha256=` followed by the hex HMAC-SHA256 of the raw request body, keyed with your signing secret."]]},{"t":"h","text":"Verify the signature"},{"t":"p","text":"Compute the HMAC over the exact bytes you received, before any JSON parsing, and compare in constant time. Reject the request if it does not match. The signature covers the body only. The `X-Webhook-Timestamp` header is not signed, so do not rely on it."},{"t":"tabs","title":"Verifying","tabs":[{"label":"Node.js","lang":"javascript","code":"const crypto = require(\"crypto\");\n\n// rawBody must be the exact bytes received (express.raw), not re-serialised JSON\nfunction verify(rawBody, header, secret) {\n  const expected = \"sha256=\" +\n    crypto.createHmac(\"sha256\", secret).update(rawBody).digest(\"hex\");\n  const a = Buffer.from(header || \"\");\n  const b = Buffer.from(expected);\n  return a.length === b.length && crypto.timingSafeEqual(a, b);\n}"},{"label":"Python","lang":"python","code":"import hmac, hashlib\n\ndef verify(raw_body: bytes, header: str, secret: str) -> bool:\n    expected = \"sha256=\" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(header or \"\", expected)"},{"label":"PHP","lang":"php","code":"function verify(string $raw, ?string $header, string $secret): bool {\n    $expected = \"sha256=\" . hash_hmac(\"sha256\", $raw, $secret);\n    return hash_equals($expected, $header ?? \"\");\n}"}]},{"t":"h","text":"Reject replays"},{"t":"p","text":"A valid request that someone captured can be sent to you again. `sent_at` sits inside the signed body, so it cannot be altered without breaking the signature. Reject an event whose `sent_at` is more than five minutes old, and remember each `correlation_id` you have processed so a repeat is ignored."},{"t":"code","lang":"javascript","title":"After the signature has been verified","code":"const event = JSON.parse(rawBody);\nconst age = Date.now() - Date.parse(event.sent_at);\nif (!(age > -60_000 && age < 5 * 60_000)) return res.status(400).end();   // too old, or from the future\nif (await alreadyProcessed(event.correlation_id)) return res.status(200).end(); // a repeat\nawait markProcessed(event.correlation_id);                                      // a database or Redis in production"},{"t":"h","text":"Delivery"},{"t":"ul","items":["Answer with any 2xx quickly. Do slow work after responding.","Up to three attempts: the first at once, then after waits of 1 and 4 seconds, on network errors and 5xx responses only. A 4xx is a rejection and is not retried. Each attempt times out after 8 seconds. Nothing is retried after that.","**An event that fails every attempt is not delivered again.** If your endpoint can be down for longer than a few seconds, look for failures in the delivery log (`GET /api/webhook/outbound/deliveries`), and catch up by reading the same information from the API, for example `GET /api/reporting/v1/messages` with `since` set to when the outage began (Scale plan).","Delivery happens after the message is handled, so a slow endpoint never delays a WhatsApp reply.","Events can arrive more than once and out of order. De-duplicate on `correlation_id` and order by the timestamps in `data`.","Use `POST /api/webhook/outbound/test` to send a signed test event, and `GET /api/webhook/outbound/deliveries` to see what your endpoint answered.","Rotate the signing secret with `POST /api/webhook/outbound/rotate-secret`. The new secret is shown once."]}]},{"id":"integrations","title":"Integrations","blocks":[{"t":"p","text":"You do not need to write code to use your key. The same key connects InboxWA to the tools most teams already use. Zapier and Make connect with a key, so they are part of Growth and Scale."},{"t":"ul","items":["**Zapier** triggers Zaps when messages arrive and sends WhatsApp messages from other apps. [Zapier guide](/docs/zapier).","**Make** builds multi-step scenarios with HTTP modules. [Make guide](/docs/make).","**Inbound webhooks** let another system push events into InboxWA. [Webhooks guide](/docs/webhooks) and [Webhook builder](/docs/webhook-builder).","**Shopify, Google Sheets and Razorpay** have their own setup pages: [Shopify](/docs/shopify), [Google Sheets](/docs/google-sheets), [Razorpay](/docs/razorpay).","**WHMCS** connects hosting billing and support to the inbox. [WHMCS connector](/docs/whmcs-connector)."]}]},{"id":"mcp","title":"AI tools (MCP)","blocks":[{"t":"p","text":"`POST /api/mcp` is a Model Context Protocol endpoint (JSON-RPC 2.0 over plain HTTP, no session) that lets any MCP client, such as Claude Desktop or Cursor, list and call the workspace actions you have chosen to expose."},{"t":"ul","items":["**It does not use an API key.** It has its own bearer token, created in the Server Actions settings, or a token from the OAuth connection flow. A missing or invalid token is answered 401 `invalid_token`.","**Only exposed actions are visible.** An action has to be marked as exposed to MCP, and an action that requires approval in the app cannot be exposed.","Only `tools/list` and `tools/call` are supported."]}]}],"x-scopes":{"groups":{"workspace":"Workspace","ai":"AI agent","admin":"Admin and billing","channel":"Channel"},"scopes":[{"key":"inbox","group":"workspace","label":"Inbox","description":"Read chats and messages; claim, release, transfer, snooze, label and note conversations.","readScope":"inbox:read"},{"key":"contacts","group":"workspace","label":"Contacts and CRM","description":"Contacts, leads and deals, tags, attributes, segments, import.","readScope":"contacts:read"},{"key":"campaigns","group":"workspace","label":"Campaigns","description":"Bulk send, scheduled sends, campaign control.","readScope":"campaigns:read"},{"key":"automations","group":"workspace","label":"Automation","description":"Workflows, auto-replies, drip sequences and their analytics.","readScope":"automations:read"},{"key":"templates","group":"workspace","label":"Templates","description":"Message templates and WhatsApp Flows.","readScope":"templates:read"},{"key":"analytics","group":"workspace","label":"Analytics","description":"Dashboards, engagement and message statistics.","readScope":"analytics:read"},{"key":"whatsapp_api","group":"channel","label":"WhatsApp send","description":"Send through the Meta Cloud API. Layered with templates and campaigns.","readScope":null},{"key":"contacts_delete","group":"workspace","label":"Delete contacts","description":"Hard delete and bulk delete of contacts.","readScope":null},{"key":"contacts_export","group":"workspace","label":"Export contacts","description":"Bulk export of contacts.","readScope":null},{"key":"campaign_approve","group":"workspace","label":"Approve campaigns","description":"Send directly and approve or reject pending bulk sends.","readScope":null},{"key":"templates_delete","group":"workspace","label":"Delete templates","description":"Delete WhatsApp templates.","readScope":null},{"key":"inbox_assigned_only","group":"workspace","label":"Assigned chats only","description":"Restricts inbox reads and sends to chats assigned to the key creator.","readScope":null},{"key":"ai_agent","group":"ai","label":"AI agent","description":"Bot configuration: knowledge base, pricing, behaviour, on and off.","readScope":"ai_agent:read"},{"key":"ai_kb_edit","group":"ai","label":"Edit knowledge base","description":"Change the FAQs the bot answers from.","readScope":null},{"key":"ai_pricing_edit","group":"ai","label":"Edit pricing","description":"Change the pricing sheets the bot quotes from.","readScope":null},{"key":"ai_behavior_edit","group":"ai","label":"Edit behaviour","description":"Change persona, system prompt and response style.","readScope":null},{"key":"ai_audit_view","group":"ai","label":"AI audit (view)","description":"Read AI usage, cost and handover audit trail.","viewOnly":true,"readScope":null},{"key":"ai_provision","group":"ai","label":"Bot on and off","description":"Turn the bot on or off for the workspace.","readScope":null},{"key":"team_management","group":"admin","label":"Team","description":"Roster, invites, roles, performance, goals, SLA, audit log.","readScope":"team_management:read"},{"key":"team_remove_member","group":"admin","label":"Remove members","description":"Remove members from the workspace.","readScope":null},{"key":"audit_view","group":"admin","label":"Audit log (view)","description":"Read the team audit log.","viewOnly":true,"readScope":null},{"key":"settings","group":"admin","label":"Settings","description":"Workspace settings, webhooks, integrations, API keys (list).","readScope":"settings:read"},{"key":"billing_view","group":"admin","label":"Billing (view)","description":"Plan, usage, credits and invoices.","viewOnly":true,"readScope":null},{"key":"billing_manage","group":"admin","label":"Billing (manage)","description":"Change plan, payment methods and top up credits.","readScope":null}],"readScopes":["inbox:read","contacts:read","campaigns:read","automations:read","templates:read","analytics:read","settings:read","team_management:read","ai_agent:read"]},"x-webhooks":[{"event":"message_received","group":"Messages","plan":"any","description":"A customer message arrives.","data":{"phone":"Customer phone number.","message_type":"text, image, audio, document and so on.","text":"Message text, truncated to 500 characters.","message_id":"WhatsApp message id (wamid).","userId":"Workspace user id the chat belongs to."},"example":{"event":"message_received","sent_at":"2026-10-02T09:22:49.000Z","correlation_id":"iwa_m1x2y3z4_a1b2c3d4e5f6","actor":"inboxwa","data":{"phone":"919876543210","message_type":"text","text":"Server is still down","message_id":"wamid.HBgM...A1","userId":6}}},{"event":"message_sent","group":"Messages","plan":"any","description":"A message leaves the workspace (team member, bot, workflow or campaign).","data":{"phone":"Customer phone number.","type":"text, template, image and so on.","text":"Message text, truncated to 500 characters.","template":"Template name when a template was sent, else null.","message_id":"WhatsApp message id (wamid).","status":"Status at send time, usually sent.","userId":"Workspace user id the chat belongs to."},"example":{"event":"message_sent","sent_at":"2026-10-02T09:22:49.000Z","correlation_id":"iwa_m1x2y3z4_a1b2c3d4e5f6","actor":"inboxwa","data":{"phone":"919876543210","type":"text","text":"We are looking into it.","template":null,"message_id":"wamid.HBgM...B7","status":"sent","userId":6}}},{"event":"contact_created","group":"Contacts","plan":"any","description":"A new contact is created.","data":{"phone":"Customer phone number.","userId":"Workspace user id."},"example":{"event":"contact_created","sent_at":"2026-10-02T09:22:49.000Z","correlation_id":"iwa_m1x2y3z4_a1b2c3d4e5f6","actor":"inboxwa","data":{"phone":"919876543210","userId":6}}},{"event":"handoff","group":"Conversations","plan":"any","description":"A chat is handed to a human.","data":{"phone":"Customer phone number.","note":"Handoff note, truncated to 500 characters.","priority":"normal or high.","userId":"Workspace user id."},"example":{"event":"handoff","sent_at":"2026-10-02T09:22:49.000Z","correlation_id":"iwa_m1x2y3z4_a1b2c3d4e5f6","actor":"inboxwa","data":{"phone":"919876543210","note":"Customer asks for a refund","priority":"high","userId":6}}},{"event":"tag_added","group":"Contacts","plan":"any","description":"A tag is added to a contact.","data":{"phone":"Customer phone number.","tag":"The tag added.","userId":"Workspace user id."},"example":{"event":"tag_added","sent_at":"2026-10-02T09:22:49.000Z","correlation_id":"iwa_m1x2y3z4_a1b2c3d4e5f6","actor":"inboxwa","data":{"phone":"919876543210","tag":"vip","userId":6}}},{"event":"message_status","group":"Messages","plan":"scale","description":"An outbound message is delivered, read or fails, with the WhatsApp timestamp.","data":{"message_id":"WhatsApp message id (wamid).","phone":"Customer phone number.","status":"sent, delivered, read or failed.","at":"When WhatsApp reported the status (ISO 8601, UTC).","error":"Failure reason when status is failed, else null."},"example":{"event":"message_status","sent_at":"2026-10-02T09:22:49.000Z","correlation_id":"iwa_m1x2y3z4_a1b2c3d4e5f6","actor":"inboxwa","data":{"message_id":"wamid.HBgM...B7","phone":"919876543210","status":"read","at":"2026-10-02T09:22:48.000Z","error":null}}},{"event":"conversation_seen","group":"Conversations","plan":"scale","description":"A team member opens a chat in the inbox or the embedded panel.","data":{"phone":"Customer phone number.","user":"{ user_id, name } of the team member who opened it.","opened_at":"ISO 8601, UTC.","marked_read":"How many unread customer messages this open marked as read."},"example":{"event":"conversation_seen","sent_at":"2026-10-02T09:22:49.000Z","correlation_id":"iwa_m1x2y3z4_a1b2c3d4e5f6","actor":"inboxwa","data":{"phone":"919876543210","user":{"user_id":41,"name":"Anita"},"opened_at":"2026-10-02T09:15:30.000Z","marked_read":3}}},{"event":"conversation_assigned","group":"Conversations","plan":"scale","description":"A chat is claimed, released or transferred.","data":{"phone":"Customer phone number.","action":"claimed, released or transferred.","assigned_to":"{ user_id, name } now holding the chat, or null after a release.","previous":"{ user_id, name } who held it before, or null.","by":"{ user_id, name } who did it, or null when the system did.","reason":"Why, for example human_claimed or handoff_transfer.","at":"ISO 8601, UTC."},"example":{"event":"conversation_assigned","sent_at":"2026-10-02T09:22:49.000Z","correlation_id":"iwa_m1x2y3z4_a1b2c3d4e5f6","actor":"inboxwa","data":{"phone":"919876543210","action":"transferred","assigned_to":{"user_id":52,"name":"Vikram"},"previous":{"user_id":41,"name":"Anita"},"by":{"user_id":41,"name":"Anita"},"reason":"handoff_transfer","at":"2026-10-02T09:40:11.000Z"}}},{"event":"conversation_resolved","group":"Conversations","plan":"scale","description":"A chat is resolved and handed back to the AI.","data":{"phone":"Customer phone number.","by":"{ user_id, name } who resolved it, or null.","previous":"{ user_id, name } who held it, or null.","at":"ISO 8601, UTC."},"example":{"event":"conversation_resolved","sent_at":"2026-10-02T09:22:49.000Z","correlation_id":"iwa_m1x2y3z4_a1b2c3d4e5f6","actor":"inboxwa","data":{"phone":"919876543210","by":{"user_id":41,"name":"Anita"},"previous":{"user_id":41,"name":"Anita"},"at":"2026-10-02T10:02:00.000Z"}}},{"event":"sla_breached","group":"Team","plan":"scale","description":"A chat misses its response target.","data":{"phone":"Customer phone number.","breach_type":"Which SLA timer was missed.","threshold_seconds":"The target that was missed.","timer_start":"When the timer started (ISO 8601, UTC).","policy_id":"The SLA policy that applied.","at":"ISO 8601, UTC."},"example":{"event":"sla_breached","sent_at":"2026-10-02T09:22:49.000Z","correlation_id":"iwa_m1x2y3z4_a1b2c3d4e5f6","actor":"inboxwa","data":{"phone":"919876543210","breach_type":"first_response","threshold_seconds":900,"timer_start":"2026-10-02T09:14:07.000Z","policy_id":3,"at":"2026-10-02T09:29:07.000Z"}}},{"event":"credits_threshold","group":"Billing","plan":"scale","description":"AI credit use crosses a level (80 and 100 percent by default) of the month’s allocation.","data":{"level":"The percentage crossed.","used_credits":"Credits used this cycle.","allocation":"Bundle plus active top-ups.","remaining_credits":"Credits left, never below zero.","cycle_start":"Start of the monthly cycle (ISO 8601, UTC)."},"example":{"event":"credits_threshold","sent_at":"2026-10-02T09:22:49.000Z","correlation_id":"iwa_m1x2y3z4_a1b2c3d4e5f6","actor":"inboxwa","data":{"level":80,"used_credits":10000,"allocation":12500,"remaining_credits":2500,"cycle_start":"2026-09-30T18:30:00.000Z"}}}],"x-changelog":[{"date":"2026-09-26","title":"The API follows the plan","items":["Starter has no API and no webhooks. Growth has the core API (sending messages, creating and updating contacts, listing templates, Zapier) and five webhook events. Scale has every call, the Reporting API and all webhook events. See Plans and API access.","A call outside the plan is refused with `api_not_in_plan` (Starter) or `api_access_not_enabled` (a Growth key calling the full API), whatever scopes the key has.","`GET /api/reporting/v1/whoami` and the key list now report `api_tier`: `none`, `core` or `full`.","New: List message templates, `GET /api/templates`."]},{"date":"2026-09-26","title":"Documentation audit","items":["The Overview no longer says every contact call works on every plan: sending and changing contacts do, reading contacts and chats with a key needs API access (Scale plan).","Phone numbers: a 10-digit number without a country code is treated as Indian (91 is added). Always send the country code.","Keys are created on the API Keys page in the sidebar (the docs and one error message said Settings). Key-management calls can also be made with a full-access key created by an admin.","The Errors guide now separates stable snake_case codes from the sentences older endpoints return, lists the codes the API really uses, and explains that a WhatsApp refusal on a send is a 500 with `errorCode`.","The Rate limits guide describes both limiters and their different response shapes.","Webhooks: the signature covers the body only, so the guide now says how to reject replays with `sent_at` and `correlation_id`, and states plainly that an event that fails all attempts is not redelivered.","The MCP guide now says it uses its own token, not an API key, and that actions needing approval cannot be exposed."]},{"date":"2026-09-25","title":"One place for the API documentation","items":["Messaging and Contacts are now documented here in full: send a message or a broadcast, and create, read, update and delete contacts, their tags, custom attributes and consent.","A guide to creating and managing keys, and an Integrations guide, replace the separate API Keys help page. The old address now opens this page.","`POST /api/send-message` now documents every message type and the response it really returns.","Sending (`/api/send-message`, `/api/bulk-send`) is not held to the default per-key request limit. A limit you set on a key still applies to everything."]},{"date":"2026-09-25","title":"Reporting API v1, key controls and webhook events","items":["Reporting API at `/api/reporting/v1`: conversations, unread and unanswered, seen-by, delivery and read receipts, replies, first-response metrics, assignment history.","Read-only scopes (`inbox:read`, `contacts:read`, `team_management:read`, `analytics:read` and more). `inbox_view` now works as a read scope for keys.","Key expiry, per-key rate limits with standard headers, rename, and rotation with a grace period.","Refused and rate-limited calls now appear in the per-key audit log.","Six new webhook events: `message_status`, `conversation_seen`, `conversation_assigned`, `conversation_resolved`, `sla_breached`, `credits_threshold`.","Webhook signing secret rotation, a test-send endpoint, and immediate application of saved changes.","Contact and chat-history reads for API keys are now controlled by the `api_access` plan feature rather than being restricted to one workspace."]}],"x-defaults":{"rate_limit_per_min":600,"max_keys":20,"key_expiry_max_days":730,"audit_retention_days":30}}