{"openapi":"3.1.0","info":{"title":"IntelliPortal Partner API","version":"1.4.0","description":"Connect client WhatsApp and Instagram accounts, send messages on their behalf, and meter usage through a versioned partner API.","contact":{"email":"support@intelliconcierge.com"}},"servers":[{"url":"https://api.intelliconcierge.dev/v1","description":"Production"}],"tags":[{"name":"Messages","description":"Send through a connected client."},{"name":"Clients","description":"Onboard and inspect channel connections."},{"name":"Usage","description":"Read monthly message consumption."},{"name":"Platform","description":"Probe public edge health and version."}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Scoped partner API key prefixed with ik_test_ or ik_live_."}},"schemas":{"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","examples":["bad_request","unauthorized","rate_limited"]},"message":{"type":"string"},"details":{"type":"object","additionalProperties":true,"description":"Optional structured validation or provider context."}}}}},"SendMessageRequest":{"type":"object","required":["client_ref","to","type"],"description":"Meta-compatible free-form message. The type value selects the same-named payload field.","properties":{"client_ref":{"type":"string","description":"Stable client identifier from your own system."},"to":{"type":"string","description":"WhatsApp recipient number or Instagram-scoped user ID."},"type":{"type":"string","enum":["text","image","video","audio","document","sticker","location","contacts","interactive","reaction"]},"text":{"type":"object","properties":{"body":{"type":"string"},"preview_url":{"type":"boolean"}}},"image":{"$ref":"#/components/schemas/CaptionedMedia"},"video":{"$ref":"#/components/schemas/CaptionedMedia"},"audio":{"$ref":"#/components/schemas/Media"},"document":{"allOf":[{"$ref":"#/components/schemas/CaptionedMedia"},{"type":"object","properties":{"filename":{"type":"string"}}}]},"sticker":{"$ref":"#/components/schemas/Media"},"location":{"type":"object","required":["latitude","longitude"],"properties":{"latitude":{"type":"number"},"longitude":{"type":"number"},"name":{"type":"string"},"address":{"type":"string"}}},"contacts":{"type":"array","items":{"type":"object","additionalProperties":true}},"interactive":{"type":"object","required":["type"],"additionalProperties":true,"properties":{"type":{"type":"string"}}},"reaction":{"type":"object","required":["message_id","emoji"],"properties":{"message_id":{"type":"string"},"emoji":{"type":"string","description":"An empty value removes an existing reaction."}}},"dry_run":{"type":"boolean","description":"Validate without delivery. Always true for ik_test_ keys."}}},"Media":{"type":"object","description":"Provide either a public HTTPS link or an uploaded media id.","properties":{"link":{"type":"string","format":"uri"},"id":{"type":"string"}}},"CaptionedMedia":{"allOf":[{"$ref":"#/components/schemas/Media"},{"type":"object","properties":{"caption":{"type":"string"}}}]},"SendTemplateRequest":{"type":"object","required":["client_ref","to","template"],"properties":{"client_ref":{"type":"string"},"to":{"type":"string"},"template":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"language":{"type":"object","properties":{"code":{"type":"string","default":"en_US"}}},"components":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"dry_run":{"type":"boolean"}}},"MessageResponse":{"type":"object","required":["success","message_id"],"properties":{"success":{"type":"boolean","const":true},"message_id":{"type":"string"},"dry_run":{"type":"boolean","description":"Present and true when no delivery was attempted."},"warnings":{"type":"array","items":{"type":"string"},"description":"Present when the message was sent but the channel could not carry part of it — a caption on an Instagram attachment, for example. The send succeeded and needs no retry, but the listed content never reached the recipient."}}},"SessionRequest":{"type":"object","required":["client_ref"],"properties":{"client_ref":{"type":"string","description":"Stable, unique identifier from your own system."}}},"SessionResponse":{"type":"object","required":["session_id","url","expires_in"],"properties":{"session_id":{"type":"string"},"url":{"type":"string","format":"uri"},"expires_in":{"type":"integer","const":600}}},"ClientSignupRequest":{"type":"object","required":["client_ref","auth_code"],"description":"Advanced Meta signup completion. Most integrations should use the hosted session flow.","properties":{"client_ref":{"type":"string"},"auth_code":{"type":"string"}}},"Client":{"type":"object","required":["client_ref","channel"],"additionalProperties":true,"properties":{"client_ref":{"type":"string"},"channel":{"type":"string","enum":["whatsapp","instagram"]},"business_name":{"type":"string"},"phone_number":{"type":"string"},"phone_number_id":{"type":"string"},"waba_id":{"type":"string"}}},"UsagePeriod":{"type":"object","required":["period","inbound_messages","outbound_messages","outbound_template_messages","total_messages"],"properties":{"period":{"type":"string","examples":["2026-07"]},"inbound_messages":{"type":"integer"},"outbound_messages":{"type":"integer"},"outbound_template_messages":{"type":"integer"},"total_messages":{"type":"integer"}}},"UsageResponse":{"type":"object","required":["current","history"],"properties":{"current":{"$ref":"#/components/schemas/UsagePeriod"},"history":{"type":"array","items":{"$ref":"#/components/schemas/UsagePeriod"}}}}}},"paths":{"/media/{media_id}":{"get":{"operationId":"downloadMedia","summary":"Download inbound media","description":"Stream media a customer sent in. WhatsApp names inbound media by id only and resolving it needs the client's Meta credentials, which partners never hold — call this instead. Instagram does not need it: its webhooks already carry a fetchable URL at attachments[].payload.url, short-lived, so download on receipt. Meta keeps inbound media for 30 days.","tags":["Messages"],"parameters":[{"name":"media_id","in":"path","required":true,"schema":{"type":"string"},"description":"The id from the webhook, e.g. messages[].image.id."},{"name":"client_ref","in":"query","required":true,"schema":{"type":"string"},"description":"Which of your clients received the media. Selects the credentials used to fetch it."}],"responses":{"200":{"description":"The file, streamed with its real Content-Type. X-Media-Sha256 carries the checksum to verify against the webhook.","headers":{"X-Media-Sha256":{"description":"SHA-256 of the file as reported by Meta.","schema":{"type":"string"}}},"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"client_ref missing (client_ref_required), or the client's channel delivers media inline (channel_media_inline).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing clients:read scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown client_ref, or Meta does not recognise the media id for this client (media_not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The client has no channel credentials on file.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Meta was unreachable or refused the download.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/messages/send":{"post":{"operationId":"sendMessage","summary":"Send a free-form message","description":"Send a Meta-compatible payload through the channel connected to client_ref. WhatsApp carries every type below. Instagram carries text, image, video, audio and document — media must be a public HTTPS URL in <type>.link, since Instagram fetches the file itself and cannot use an uploaded media id (code media_link_required); caption and filename are returned in `warnings` because an Instagram attachment has nowhere to put them. Templates, sticker, location, contacts, interactive and reaction are WhatsApp-only (code channel_type_unsupported).","tags":["Messages"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageRequest"}}}},"responses":{"200":{"description":"Message accepted or dry-run validated.","headers":{"X-Request-Id":{"description":"Opaque request correlation ID.","schema":{"type":"string","format":"uuid"}},"X-RateLimit-Limit":{"description":"Edge request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current edge window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Trial and grace have ended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing messages:send scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown client_ref.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Channel provider rejected the message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/messages/send-template":{"post":{"operationId":"sendTemplateMessage","summary":"Send a WhatsApp template","tags":["Messages"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendTemplateRequest"}}}},"responses":{"200":{"description":"Template send accepted or dry-run validated.","headers":{"X-Request-Id":{"description":"Opaque request correlation ID.","schema":{"type":"string","format":"uuid"}},"X-RateLimit-Limit":{"description":"Edge request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current edge window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Trial and grace have ended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing messages:send scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown client or template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Channel provider rejected the message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/embedded-signup/sessions":{"post":{"operationId":"createWhatsAppSignupSession","summary":"Create a hosted WhatsApp signup session","description":"Returns a single-use Intelli-hosted URL valid for 10 minutes.","tags":["Clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionRequest"}}}},"responses":{"201":{"description":"Session created.","headers":{"X-Request-Id":{"description":"Opaque request correlation ID.","schema":{"type":"string","format":"uuid"}},"X-RateLimit-Limit":{"description":"Edge request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current edge window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"401":{"description":"Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing clients:write scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Client reference or number is already connected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/instagram-connect/sessions":{"post":{"operationId":"createInstagramConnectSession","summary":"Create a hosted Instagram connect session","description":"Returns a single-use Intelli-hosted URL valid for 10 minutes.","tags":["Clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionRequest"}}}},"responses":{"201":{"description":"Session created.","headers":{"X-Request-Id":{"description":"Opaque request correlation ID.","schema":{"type":"string","format":"uuid"}},"X-RateLimit-Limit":{"description":"Edge request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current edge window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"401":{"description":"Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing clients:write scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Client reference or account is already connected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/clients":{"get":{"operationId":"listClients","summary":"List connected clients","tags":["Clients"],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":20,"minimum":1}},{"name":"offset","in":"query","schema":{"type":"integer","default":0,"minimum":0}}],"responses":{"200":{"description":"Paginated client records.","headers":{"X-Request-Id":{"description":"Opaque request correlation ID.","schema":{"type":"string","format":"uuid"}},"X-RateLimit-Limit":{"description":"Edge request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current edge window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/Client"}},{"type":"object","additionalProperties":true}]}}}},"401":{"description":"Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing clients:read scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/clients/{client_ref}":{"get":{"operationId":"getClient","summary":"Get a client by reference","tags":["Clients"],"parameters":[{"name":"client_ref","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Connected client.","headers":{"X-Request-Id":{"description":"Opaque request correlation ID.","schema":{"type":"string","format":"uuid"}},"X-RateLimit-Limit":{"description":"Edge request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current edge window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Client"}}}},"401":{"description":"Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing clients:read scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown client_ref.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/clients/signup":{"post":{"operationId":"completeWhatsAppSignup","summary":"Complete a Meta signup code exchange","description":"Advanced endpoint used to complete Meta Embedded Signup. Most integrations should use /embedded-signup/sessions.","tags":["Clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientSignupRequest"}}}},"responses":{"201":{"description":"Client connected.","headers":{"X-Request-Id":{"description":"Opaque request correlation ID.","schema":{"type":"string","format":"uuid"}},"X-RateLimit-Limit":{"description":"Edge request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current edge window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Client"}}}},"400":{"description":"Invalid code or request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing clients:write scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Client or number is already connected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/usage":{"get":{"operationId":"getUsage","summary":"Get monthly message usage","description":"Returns the current UTC month and recent closed months. Test-key and dry-run traffic is excluded.","tags":["Usage"],"responses":{"200":{"description":"Usage rollups.","headers":{"X-Request-Id":{"description":"Opaque request correlation ID.","schema":{"type":"string","format":"uuid"}},"X-RateLimit-Limit":{"description":"Edge request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current edge window.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"401":{"description":"Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/status":{"get":{"operationId":"getStatus","summary":"Probe public API edge health and version","tags":["Platform"],"security":[],"responses":{"200":{"description":"The API edge is reachable.","content":{"application/json":{"schema":{"type":"object","required":["status","version"],"properties":{"status":{"type":"string","const":"ok"},"version":{"type":"string","const":"v1"}}}}}}}}}}}