Authentication
Org API keys, scopes, acting on a child org, and how PII is masked
Any Wexio organization on a qualifying plan can mint an Org API key and drive its own inbox from a backend. There is no separate partner credential - a partner is simply an org whose key may also act on the client orgs it manages.
The API needs Standard or higher, and MCP needs Pro.
| Surface | Plan |
|---|---|
| GraphQL API with a key | Standard, Pro or Enterprise |
| MCP server | Pro or Enterprise |
| Webhooks | Pro or Enterprise |
This is checked on every call, not only when the key is minted. Downgrade and existing keys stop working; upgrade and they work again. Keys are never revoked for a plan change, so you do not have to re-issue them - see what a downgrade does.
Tech providers and their client organisations are not affected: they are billed by usage rather than a retail plan.
Request Headers
Authorization: Bearer wx_9f3c1a2b4d5e6f70_kQ8sN1pT4vR7wZ2yB5eH8jL0mQ3sV6x| Header | Required | Value |
|---|---|---|
Authorization | Yes | Bearer followed by the key, wx_<keyId>_<secret>. |
X-Wexio-Org | No | A managed child org to act on instead of your own. Only a tech provider's key may use it. |
A key always acts on its own org unless X-Wexio-Org names a child the caller actually manages.
The raw X-Wexio-Org header is never trusted on its own. The server checks that the key's org is a TECH_PROVIDER and that the named org is one of its managed children. If it is not, the request fails - a key cannot reach sideways into an unrelated org by guessing an ID.
Scopes
Every key carries an explicit list of scopes, chosen at mint time. A call is rejected unless the key holds the scope the operation requires.
| Scope | Unlocks |
|---|---|
MESSAGES_READ | Reading conversations, messages, search, media and media counts |
MESSAGES_SEND | Sending, editing and deleting messages; reactions; media upload |
CONVERSATIONS_MANAGE | Inbox state: read/unread, close/reopen, assign, block, labels, collections, AI toggle, custom fields |
CONTACTS_READ | Reading and searching contacts; reading people-field definitions and values |
CONTACTS_MANAGE | Creating and updating contacts; managing field definitions and values |
CONTACTS_ERASE | Permanently erasing a contact (GDPR right to erasure) |
CONVERSATIONS_DELETE | Permanently deleting chats and conversations, with their messages and media |
CHANNELS_MANAGE | Connecting and disconnecting channels; listing them; syncing WhatsApp templates |
NOTES_WRITE | Internal team notes - both writing them and seeing them at all |
PII_READ | Unmasked customer identity: names, phones, emails, avatars, author, location, field values |
TEAM_READ | Listing org members |
TEAM_MANAGE | Adding, re-roling and removing members |
CONTENT_READ | Reading Help articles, News posts, and their folders, categories and tags |
CONTENT_MANAGE | Creating, editing, publishing and deleting that content |
PARTNER_ADMIN | Tech providers only: provisioning client orgs, the fan-out webhook, the usage rollup |
Scopes are independent. A key with MESSAGES_SEND but not MESSAGES_READ can send and never read.
The two destructive scopes are deliberately separate from the manage scopes they sit next to:
CONTACTS_ERASEis not part ofCONTACTS_MANAGE- a key that maintains contacts day to day should not be able to destroy them.CONVERSATIONS_DELETEis not part ofCONVERSATIONS_MANAGE- triaging the inbox and deleting it permanently are different jobs.
Grant each only to the integration that actually needs it. Both are irreversible, and some operations need both: deleting a conversation with removePeople: true is a GDPR erase and requires CONVERSATIONS_DELETE and CONTACTS_ERASE.
PARTNER_ADMIN is the one scope that only means something on a tech provider org (kind TECH_PROVIDER) - it gates provisioning client orgs, managing the fan-out webhook, and reading the usage rollup. On any other org's key it unlocks nothing, including on a PARTNERSHIP distribution partner.
Members are not scoped. A human logged into the dashboard holds every scope implicitly - their authority comes from their role. Scopes exist to constrain machines.
Pick the Narrowest Set
A key is a long-lived credential on a live customer inbox. Mint one key per job:
| The integration… | Scopes |
|---|---|
| Mirrors conversations into your product, read-only | MESSAGES_READ |
| Mirrors and replies | MESSAGES_READ, MESSAGES_SEND |
| Replies and triages the inbox | add CONVERSATIONS_MANAGE |
| Syncs a CRM both ways | add CONTACTS_READ, CONTACTS_MANAGE, PII_READ |
| Handles data-deletion requests | add CONTACTS_ERASE |
| Deletes conversations permanently | add CONVERSATIONS_DELETE |
| Manages the web widget or connects client channels | add CHANNELS_MANAGE |
| Publishes Help or News content | add CONTENT_READ, CONTENT_MANAGE |
| Provisions client orgs (tech providers) | add PARTNER_ADMIN |
PII Masking
Customer identity is withheld from any key that does not hold PII_READ. The request still succeeds - the fields simply come back null, and participant lists come back empty.
What PII_READ gates:
- Names, phone numbers, email addresses, avatars
- Chat participants and message authors
- Location payloads
- People-field values
Masking is not an error, and a masked read is indistinguishable from an unset value. If identity fields are unexpectedly null, check the key's scopes before assuming the data is missing.
Filters Are Refused, Not Just Masked
A masked key cannot use identity as a filter either - otherwise the filter itself would be an oracle on the data the masking hides.
Attempt without PII_READ | Result |
|---|---|
| A rule on contact name, assignee, or a person profile field | 403 |
Free-text contact search | 403 |
| Reading a collection's rule values for those fields, or for message text | Returned as null |
ChatCollection.createdBy | null |
This is why a masked key sees a collection it cannot reconstruct: the collection still works and still filters, but the values inside its rules come back empty. The filtering happens server-side on data the key may not read.
Internal Notes
Internal notes are team-only messages that never reach the customer. They are invisible to a key without NOTES_WRITE - not redacted, absent: excluded from reads, from previews, and their media attachments are hidden too.
With NOTES_WRITE, a key can read notes and post them by sending a message with internal: true, or by adding a note to a conversation.
There is no separate notes-read scope - NOTES_WRITE gates both directions. Without it:
- notes, system rows and internal actions are absent from reads;
- a message-text rule never matches a note;
- storing a collection with message-level rules is 403;
- labelling a note answers not-found.
How Requests Are Checked
In order:
- Key valid? Missing, malformed, or revoked → 401. A revoked key stops working immediately.
- Target org allowed?
X-Wexio-Orgnaming an org you don't manage → 403. - Scope held? The operation's required scope missing from the key → 403, naming the scope.
- PII? Masked in the response when
PII_READis absent, rather than refused.
What Happens on a Downgrade
Nothing is deleted, and nothing needs re-issuing. Access simply stops until the plan is restored.
| Keys | Kept. Every call returns PLAN_FEATURE_NOT_ALLOWED with the feature that is missing: apiAccess, mcp or webhooks. |
| MCP | 403 with {"error":"plan_feature_not_allowed","feature":"mcp"}. |
| Webhook delivery | Paused automatically. The connections themselves are kept. |
| Inbound webhooks | Rejected with 400 WEBHOOK_UNAVAILABLE. |
| Still available on any plan | Listing, viewing, pausing and deleting connections, and reading delivery history. |
| Needs Pro | Creating, editing or reactivating a connection. |
Upgrading restores everything, with webhook delivery resuming within about a minute.
Because keys survive, a lapsed subscription is a pause rather than a migration. Your integration starts working again on its own once the plan is back - there is no key rotation or reconfiguration to redo.
Managing Keys
Minting, listing and revoking keys is dashboard-only - a member login on the org, under Settings → Webhooks and API → API keys. These operations cannot be called with a key, so a leaked key can never mint another. See API keys.
Store the secret at mint time. The full wx_... value is returned once and never again. Later listings show only keyId, label, scopes, status and last-used time.
To rotate: mint a new key, deploy it, confirm traffic on the new keyId, then revoke the old one.
Realtime Is Webhooks
An API key cannot open a GraphQL subscription. For live updates, register an outbound webhook and receive signed events over HTTP.