# Wabi > Wabi runs your WhatsApp Business Account on Meta's Cloud API: managed webhooks, a live team inbox, localized templates, AI support agents with traces, and usage and cost analytics, from the dashboard, the REST API, MCP or the CLI. Bring your own WABA, pay Meta at cost. ## Connect and authenticate - MCP (streamable HTTP): `https://getwabi.dev/api/mcp`. One tool per op. OAuth sign-in through the browser (see https://getwabi.dev/.well-known/oauth-protected-resource), or an API key. - REST: `POST https://getwabi.dev/api/v1/{op}` with the op's input as JSON; read-only ops also accept GET with `?input=`. `GET https://getwabi.dev/api/v1` lists every op with its JSON Schema. - CLI: `npx @meterapp/wabi [--field value] [--json '']`; `npx @meterapp/wabi login` signs in with the browser and saves a key. In CI set `WABI_API_KEY`. - API keys: `Authorization: Bearer wabi_…`. Create one in Integrations, with `npx @meterapp/wabi login` or `create_api_key`. A key acts with its creator's role (viewer < member < admin < owner) and can be narrowed to scopes: `:read`, `:write`, `:*`, `*:read`, and `billing:purchase` (implied by nothing). With a session or OAuth token, `x-wabi-org: ` picks the organization. - Errors are RFC 9457 problem+json with a `code` and `request_id`; a 402 carries a checkout_url. See https://getwabi.dev/docs/errors. Claude Code: ```bash claude mcp add --transport http wabi https://getwabi.dev/api/mcp ``` ## Workflows 1. Orient: `get_onboarding` (the setup checklist and the next step), `get_account` (plan and usage), `get_organization`. 2. Try it: `get_sandbox` → send a template to a test customer (or `simulate_inbound_message` to open their window) → watch statuses and replies arrive. 3. Go live: connect a WhatsApp Business Account in Settings → Accounts (Embedded Signup or WABA id + system-user token); webhooks are managed for you. 4. Team: `list_members`, `create_team`, `share_resource` / `set_resource_visibility` for WhatsApp accounts and agents; invitations and role changes are done by a person in the dashboard. 5. Developer surfaces: `create_api_key` with scopes for a backend; customer webhook endpoints in Integrations. 6. Billing: `get_pricing` → `create_checkout_link` for a human to pay; `get_billing_link` for invoices and cancellation; `set_budget_cap` for overage. ## Rules - Start with `get_onboarding`: it says what is set up, what is next and which op does it. - Test in the sandbox first (`get_sandbox`, `simulate_inbound_message`). Never message real customers to test something. - Respect WhatsApp's rules: free-form messages only inside a customer's 24-hour window (the customer wrote in the last 24 hours); otherwise send an approved template. Never message someone without the opt-in Meta requires, and honour opt-outs. - Meta's message fees are billed by Meta to the customer's WhatsApp Business Account; never describe them as a charge from us, and never invent a price. Quote get_pricing. - Never buy, change or cancel a plan on your own initiative. Quote the plan and price from get_pricing, and only call subscribe or cancel_subscription with confirm: true after a human approved it; otherwise give them the create_checkout_link URL. - Inviting and removing members and changing roles are done by a person in the dashboard; do not try to work around a 403. - Use read-only ops freely; every write is recorded in the activity log with your surface. - When a call fails in a way that looks like a Wabi bug, report_issue with the error's request_id, the op and context; when the human wishes Wabi did something it cannot, request_feature. If they come back filed: false with `similar`, upvote_feedback the match instead. Never invent a review: submit_review only with the human's rating and words. ## Sandbox Every organization has a simulated WhatsApp number on every plan, backed by a Meta simulator: nothing reaches real WhatsApp users, nothing is charged, and sandbox messages do not count against the plan. `get_sandbox` returns its account id, its phone number id (the sender), the test customers (15550100001, 15550100002, 15550100003, 15550100004, 15550100005) and the starter template `hello_world` (en_US, approved). Sends report sent at once, delivered after ~1 s and read after ~3 s; the test customer replies after ~5 s. `simulate_inbound_message` makes a test customer send any text, through the same pipeline as Meta traffic. Free-form text outside the 24-hour window fails with 131047, as on Meta. New templates are reviewed after ~60 s. Recipients ending in 0000 (e.g. 15550100000) fail with 131026. Guide: https://getwabi.dev/docs/sandbox. ## Pricing Units: messages: every inbound and outbound WhatsApp message the platform processes (sandbox messages are free). ai_replies: one AI agent turn that writes a customer message or a suggested reply on our model key (bring-your-own-key turns are free). Meta bills its own per-message fees to your WhatsApp Business Account directly. We show them at cost and never mark them up or bill them. Yearly is two months free (10x the monthly price). The full pricing document (JSON, no credential, the same shape on every Meter product): `GET https://getwabi.dev/api/v1/pricing`; MCP `get_pricing` (also on the no-auth catalog server https://getwabi.dev/api/mcp/catalog); CLI `npx @meterapp/wabi pricing --json`; human page: https://getwabi.dev/pricing. - Free (`free`): free. 1,000 messages and 100 AI replies a month; 1 live number(s) plus the sandbox, 2 members, 30-day history. For trying Wabi on one number. - Starter (`starter`): $29/month or $290/year. 10,000 messages and 1,000 AI replies a month; 1 live number(s) plus the sandbox, 5 members, 365-day history. For a small team answering customers on one number. Buy: https://getwabi.dev/checkout?plan=starter&ref=agent - Growth (`growth`): $99/month or $990/year. 50,000 messages and 5,000 AI replies a month; 3 live number(s) plus the sandbox, 15 members, 365-day history. For businesses automating support and outreach across numbers. Buy: https://getwabi.dev/checkout?plan=growth&ref=agent - Scale (`scale`): $299/month or $2,990/year. 250,000 messages and 25,000 AI replies a month; 10 live number(s) plus the sandbox, unlimited members, 365-day history. For operators running several brands, markets or locations. Buy: https://getwabi.dev/checkout?plan=scale&ref=agent - Enterprise (`enterprise`): custom. custom messages and custom AI replies a month; custom live number(s) plus the sandbox, unlimited members, custom history. For larger volumes, security reviews and custom terms. Overage: $1 per 1,000 messages and $0.02 per AI reply past the allowance, up to a monthly budget cap (default $58, 2x the plan price; change it on the billing page). Non-critical sends stop at the cap. Free: No overage: sends past the monthly allowance are refused until the next month or an upgrade. Buying a plan (never collect card details; the human pays on Stripe): - MCP `create_checkout_link`, CLI `wabi upgrade --plan growth [--yearly]`, REST `POST /api/v1/billing/checkout` return a Stripe Checkout URL for a human. Nothing is charged until they complete it. - Before signup: create_checkout_link needs no account: without a credential it returns a Stripe Checkout URL for a human. After paying they sign in to Wabi with the same email; the subscription attaches to their organization on return (or claim_checkout {session_id}). - With a saved card: subscribe {plan, interval, confirm: true} changes the plan on the organization's saved card, only after a human approved the price. - Account and invoices: get_billing_link (invoices, card, cancel); get_account (plan, usage, what the next plan adds). - Enterprise: Email jonathan@getwabi.dev. ## Customer webhooks Signed events to HTTPS endpoints added in Integrations. Headers: `webhook-id`, `webhook-timestamp`, `webhook-signature` (Standard Webhooks). Body: `{ id, type, occurred_at, org_id, subject, data }`. Guide: https://getwabi.dev/docs/webhooks. - Endpoints are HTTPS URLs added in Integrations in the dashboard, each with its own signing secret and an optional list of event types (none: every event). - Delivery is at least once: the same event can arrive more than once, so deduplicate on webhook-id. Order is not guaranteed; use occurred_at. - Answer with any 2xx within 10 seconds and do slow work afterwards. Anything else, or no answer, is retried with exponential backoff. - After the last retry the delivery is marked dead. An endpoint that keeps failing is marked failing and its owners are told; every attempt, with its status code and latency, is in the endpoint's delivery log. - Signatures follow the Standard Webhooks specification, so its libraries verify Wabi deliveries as they are. Event types: - `message.received`: A customer sent a message to one of your numbers (text, media, location, interactive reply, reaction). - `message.sent`: Meta accepted an outbound message for delivery. - `message.delivered`: An outbound message reached the customer's phone. - `message.read`: The customer read an outbound message (when their read receipts are on). - `message.failed`: An outbound message failed, with Meta's error code and whether a retry could help. - `conversation.needs_reply`: A customer turn is waiting for a substantive answer from your business. - `conversation.escalated`: An AI agent or a rule handed the conversation to a person, with the reason and a summary. - `conversation.resolved`: The conversation was marked resolved. - `window.expiring`: A customer's 24-hour service window closes soon; after it, only templates can be sent. - `template.approved`: Meta approved a template in one language. - `template.rejected`: Meta rejected a template in one language, with the reason. - `template.quality_changed`: Meta changed a template's quality rating or paused or disabled it. - `flow.submitted`: A customer completed a WhatsApp Flow; the payload carries their answers. - `location.received`: A customer shared a location (a single pin; continuous live location is not available on the Cloud API). - `agent.session.completed`: An AI agent session ended: resolved, escalated or timed out, with tokens and cost. - `agent.execution.failed`: An AI agent turn failed (model or tool error); the conversation is left for a person. ## Feedback and reviews Feature requests are public on https://getwabi.dev/feedback; issue reports are private to the reporter's organization and the Wabi team. Reviews are public on https://getwabi.dev/reviews, one per organization. Guide: https://getwabi.dev/docs/feedback. | action | MCP tool | CLI | REST | | --- | --- | --- | --- | | list / search the board | `list_feedback` | `npx @meterapp/wabi feedback list` | `GET https://getwabi.dev/api/v1/feedback` (public) | | request a feature | `request_feature` | `npx @meterapp/wabi feedback request ""` | `POST https://getwabi.dev/api/v1/feedback/features` | | report an issue | `report_issue` | `npx @meterapp/wabi feedback report "<title>" [--last]` | `POST https://getwabi.dev/api/v1/feedback/issues` | | upvote | `upvote_feedback` | `npx @meterapp/wabi feedback upvote <id>` | `POST https://getwabi.dev/api/v1/feedback/{id}/votes` | | post / edit a review | `submit_review` | `npx @meterapp/wabi review --rating 5 --body "…"` | `PUT https://getwabi.dev/api/v1/reviews/me` | ## Setup ### Claude Code ```bash claude mcp add --transport http wabi https://getwabi.dev/api/mcp ``` ### Codex ```toml # ~/.codex/config.toml [mcp_servers.wabi] url = "https://getwabi.dev/api/mcp" bearer_token_env_var = "WABI_API_KEY" ``` ### Cursor (.cursor/mcp.json) ```json { "mcpServers": { "wabi": { "url": "https://getwabi.dev/api/mcp", "headers": { "Authorization": "Bearer wabi_…" } } } } ``` ### stdio-only MCP hosts ```json { "mcpServers": { "wabi": { "command": "npx", "args": [ "-y", "@meterapp/wabi", "mcp" ], "env": { "WABI_API_KEY": "wabi_…" } } } } ``` ### CLI ```bash npx @meterapp/wabi login npx @meterapp/wabi get_sandbox npx @meterapp/wabi simulate_inbound_message --text "Hi, where is my order?" ``` ## Errors Every failure is `application/problem+json` (RFC 9457): `type`, `title` (the code), `status`, `detail`, `code`, `request_id`. - 400 `invalid_json`: The body (or ?input for GET) is not valid JSON. - 400 `invalid_input`: The input failed the op's schema. `issues` lists each problem with its path. - 400 `invalid_slug`: The name produced no usable slug. Use letters, digits and dashes. - 400 `confirm_mismatch`: A destructive op needs its target's name typed back in `confirm`, and it did not match. - 400 `principal_required`: Name exactly one team or person to share with or remove. - 400 `same_person`: The op cannot target yourself (for example transferring ownership to yourself). - 400 `no_person`: No member of this organization has that email address or id. - 400 `invalid_grant`: CLI sign-in: the device code is unknown or was already used. Run login again. - 400 `expired_token`: CLI sign-in: the device code expired before it was approved. Run login again. - 400 `access_denied`: CLI sign-in: the person declined the request in the browser. - 401 `unauthorized`: No credential. Send `Authorization: Bearer wabi_…`. - 401 `invalid_key`: The API key does not exist or was revoked. - 401 `expired_key`: The API key passed its expiry date. Create a new one. - 401 `invalid_token`: The OAuth access token is invalid or expired. Refresh it and retry. - 402 `payment_failed`: Stripe declined the charge for a plan change. Ask a human to update the card (get_billing_link) or pay through create_checkout_link. - 403 `forbidden`: The op needs a higher role than this credential has. `required_role` names it. - 403 `scope_missing`: The API key's scopes do not cover this op. `required_scope` names the scope it needs. - 403 `scope_escalation`: A scoped key tried to create a key with wider scopes than its own. - 403 `dashboard_only`: A person does this in the dashboard (inviting members, changing roles); it is not available to keys. - 403 `person_required`: Only a signed-in person can do this, not an API key. - 403 `no_organization`: The signed-in user is not a member of the requested organization (or of any). - 404 `unknown_op`: No op has that name. `GET /api/v1` lists them. - 404 `not_found`: The account, contact, template, agent or other record is not in this organization, or you cannot see it. - 405 `method_not_allowed`: A write op was sent with GET. Use POST. - 409 `organization_exists`: An organization with that slug already exists. - 409 `team_exists`: A team with that name already exists in this organization. - 409 `already_member`: That person is already a member of the organization. - 409 `last_owner`: An organization keeps at least one owner: the last owner cannot leave or be demoted. - 409 `sandbox_not_provisioned`: The organization's sandbox does not exist yet. Call get_sandbox first; it creates it. - 409 `already_subscribed`: The organization is already on that plan and interval. get_billing shows the subscription. - 409 `subscription_active`: The organization already pays for a plan; change it with subscribe or the billing portal. - 409 `no_subscription`: There is no paid subscription to cancel or resume. - 409 `checkout_incomplete`: The Stripe Checkout session was not paid yet. - 409 `already_claimed`: That Checkout session is already attached to an organization. - 413 `context_too_large`: The context attached to an issue report is over 128 KB. - 428 `authorization_pending`: CLI sign-in: the person has not approved the code yet. Poll again after `interval` seconds. - 429 `rate_limited`: Too many requests. Wait for `Retry-After` seconds and retry. - 500 `internal_error`: Something failed on our side. Retry, and quote `request_id` if it persists. - 503 `billing_unavailable`: Billing is not configured on this server yet. Email support to subscribe. Meta's own error codes, on message timelines and message.failed events: - 131047: Free-form message outside the 24-hour customer service window. Send an approved template instead. - 131026: Undeliverable: the number is not on WhatsApp, blocked the business, or Meta held the message back. - 130429, 131056: Rate limited by Meta (throughput or per-recipient pair rate). Retried with backoff. - 132000–132999: Template problem: not approved in that language, paused, disabled, or the parameters do not match. - 190: The WhatsApp Business Account's access token expired or was revoked. Reconnect the account. ## Ops reference Call any op with `POST https://getwabi.dev/api/v1/{name}` and its input as JSON. Read-only ops also accept GET with `?input=<url-encoded JSON>`. ### get_organization Get organization. Read-only. Group: organization. Scope: organization:read. The organization this credential acts in: id, slug, name, default timezone and language (used by the inbox, reports and AI agents), and the caller's role. Call first to orient yourself; get_account adds the plan and usage. Input schema: ```json { "type": "object", "properties": {} } ``` ### update_organization Update organization. Writes. Needs the admin role. Group: organization. Scope: organization:write. Change the organization's name, default timezone (IANA, e.g. Europe/Madrid: business hours, reports, quiet hours) or default language (BCP 47: AI agent replies and template fallbacks when a contact has none). Owners and admins. Input schema: ```json { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 100 }, "timezone": { "type": "string", "maxLength": 64 }, "default_locale": { "type": "string", "minLength": 2, "maxLength": 35 } } } ``` ### list_activity List activity. Read-only. Group: organization. Scope: organization:read. Every change made through the platform, newest first: who (a person, an API key, an AI agent, a worker), from which surface (dashboard, api, mcp, cli, worker, ai) and what. The audit log (list_audit_log) has the sensitive changes with their details. Input schema: ```json { "type": "object", "properties": { "limit": { "default": 50, "description": "Page size, at most 200.", "type": "integer", "minimum": 1, "maximum": 200 }, "offset": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 } } } ``` ### get_onboarding Get onboarding checklist. Read-only. Group: organization. Scope: organization:read. The organization's setup checklist, in order: send a message from the sandbox number, receive a reply, connect a live WhatsApp Business Account, get a template approved, deploy an AI agent, connect a developer surface (API key, MCP or CLI), invite a teammate. Each step has done, a one-line how-to for an agent (which op or command does it) and the dashboard path. Use it to tell a person what to do next in Wabi. Input schema: ```json { "type": "object", "properties": {} } ``` ### get_referral_stats Referral signups (staff). Read-only. Group: organization. Scope: organization:read. Wabi team only. Signups by first-touch referral: how many new organizations arrived through a `?ref=<name>` link (a partner, a docs page, an agent surface) or utm_* campaign, per referral_ref, utm_source and utm_medium, in total and within the last `days`. Reads every organization (the only cross-organization read besides the feedback board), returns counts only, never organization names. Input schema: ```json { "type": "object", "properties": { "days": { "default": 30, "description": "Window for signups_since. Default 30.", "type": "integer", "minimum": 1, "maximum": 3650 } } } ``` ### list_members List members. Read-only. Group: members. Scope: members:read. Everyone in this organization with their email, role (owner, admin, member, viewer) and teams. Read-only: inviting, removing people and changing roles are done by a person in the dashboard (Settings → Members). Input schema: ```json { "type": "object", "properties": {} } ``` ### list_invites List invites. Read-only. Needs the admin role. Group: members. Scope: members:read. Pending invitations to this organization (email, role, teams, who invited, when it expires). Owners and admins. Input schema: ```json { "type": "object", "properties": { "include_closed": { "default": false, "description": "Also list accepted, revoked and expired invites.", "type": "boolean" } } } ``` ### list_teams List teams. Read-only. Group: members. Scope: members:read. Teams in this organization with their members (maintainer or member) and the resources (WhatsApp accounts, AI agents) shared with each team. Input schema: ```json { "type": "object", "properties": {} } ``` ### create_team Create team. Writes. Group: members. Scope: members:write. Create a team (a group of members to share WhatsApp accounts and agents with, and to route conversations to). You become its maintainer. Members and above. Input schema: ```json { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 100 }, "slug": { "description": "URL-safe id; derived from the name when omitted.", "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,47}$" }, "description": { "default": "", "type": "string", "maxLength": 1000 } }, "required": [ "name" ] } ``` ### update_team Update team. Writes. Group: members. Scope: members:write. Rename a team or change its description. Its maintainers, owners and admins. Input schema: ```json { "type": "object", "properties": { "team": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Team slug or id." }, "name": { "type": "string", "minLength": 1, "maxLength": 100 }, "description": { "type": "string", "maxLength": 1000 } }, "required": [ "team" ] } ``` ### delete_team Delete team. Writes. Group: members. Scope: members:write. Delete a team. Its members stay in the organization but lose what was shared with the team. Input schema: ```json { "type": "object", "properties": { "team": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Team slug or id." } }, "required": [ "team" ] } ``` ### set_team_member Add to team. Writes. Group: members. Scope: members:write. Add an organization member to a team, or change their team role (maintainer manages the team; member is in it). Its maintainers, owners and admins. Input schema: ```json { "type": "object", "properties": { "team": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Team slug or id." }, "user": { "type": "string", "minLength": 3, "maxLength": 320, "description": "An organization member's user id or email address." }, "role": { "default": "member", "type": "string", "enum": [ "maintainer", "member" ] } }, "required": [ "team", "user" ] } ``` ### remove_team_member Remove from team. Writes. Group: members. Scope: members:write. Take someone out of a team (they stay in the organization). Its maintainers, owners and admins; anyone can leave a team themselves. Input schema: ```json { "type": "object", "properties": { "team": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Team slug or id." }, "user": { "type": "string", "minLength": 3, "maxLength": 320, "description": "An organization member's user id or email address." } }, "required": [ "team", "user" ] } ``` ### list_resource_access List resource access. Read-only. Group: members. Scope: members:read. Who can see a shareable resource (a WhatsApp account or an AI agent): its visibility (org or restricted), its creator, and every grant to a team or person with its access (read, write, admin). Owners and admins always have admin. Input schema: ```json { "type": "object", "properties": { "resource_type": { "type": "string", "enum": [ "whatsapp_account", "agent" ], "description": "whatsapp_account or agent." }, "resource_id": { "type": "string", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" } }, "required": [ "resource_type", "resource_id" ] } ``` ### set_resource_visibility Set resource visibility. Writes. Group: members. Scope: members:write. org: every organization member sees the resource (viewers read, members write). restricted: only its creator, owners and admins, and the teams and people it is shared with (share_resource). Works on WhatsApp accounts and AI agents. Needs admin access to the resource. Input schema: ```json { "type": "object", "properties": { "resource_type": { "type": "string", "enum": [ "whatsapp_account", "agent" ], "description": "whatsapp_account or agent." }, "resource_id": { "type": "string", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }, "visibility": { "type": "string", "enum": [ "org", "restricted" ] } }, "required": [ "resource_type", "resource_id", "visibility" ] } ``` ### share_resource Share resource. Writes. Group: members. Scope: members:write. Give a team or a person read, write or admin access to a WhatsApp account or AI agent (replacing what that team or person had). read: see it and its data; write: use it (send from the account, edit the agent); admin: also change its settings and who it is shared with. Viewers never get past read. Needs admin access to the resource. Input schema: ```json { "type": "object", "properties": { "resource_type": { "type": "string", "enum": [ "whatsapp_account", "agent" ], "description": "whatsapp_account or agent." }, "resource_id": { "type": "string", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }, "team": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Team slug or id." }, "user": { "type": "string", "minLength": 3, "maxLength": 320, "description": "An organization member's user id or email address." }, "access": { "type": "string", "enum": [ "read", "write", "admin" ] } }, "required": [ "resource_type", "resource_id", "access" ] } ``` ### unshare_resource Unshare resource. Writes. Group: members. Scope: members:write. Remove a team's or person's grant on a WhatsApp account or AI agent. They keep whatever their organization role gives them (nothing, on a restricted resource). Needs admin access to the resource. Input schema: ```json { "type": "object", "properties": { "resource_type": { "type": "string", "enum": [ "whatsapp_account", "agent" ], "description": "whatsapp_account or agent." }, "resource_id": { "type": "string", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }, "team": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Team slug or id." }, "user": { "type": "string", "minLength": 3, "maxLength": 320, "description": "An organization member's user id or email address." } }, "required": [ "resource_type", "resource_id" ] } ``` ### list_audit_log List audit log. Read-only. Needs the admin role. Group: members. Scope: members:read. Membership, role, team, invite, API key, billing, settings and sharing changes in this organization, newest first: who (a person, an API key, an OAuth client or the system), what, to what, and when. Owners and admins. The activity log (list_activity) has content changes. Input schema: ```json { "type": "object", "properties": { "action": { "description": "Only this action or prefix, e.g. member. or invite.accepted.", "type": "string", "maxLength": 100 }, "limit": { "default": 50, "description": "Page size, at most 200.", "type": "integer", "minimum": 1, "maximum": 200 }, "offset": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 } } } ``` ### list_api_keys List API keys. Read-only. Group: keys. Scope: keys:read. API keys of this organization (prefix only; the full key is shown once at creation), each with the role it acts with, its scopes and who created it. Owners and admins see every key; members see their own. Also returns available_scopes. Input schema: ```json { "type": "object", "properties": {} } ``` ### create_api_key Create API key. Writes. Group: keys. Scope: keys:write. Create an organization API key for your backend, the CLI, an MCP host or CI. It acts with your role (an owner's key acts as admin), optionally narrowed to scopes, and keeps working if you leave the organization; admins can revoke it. The full key (wabi_…) is returned only once; store it as a secret and never ship it to a browser or app. Input schema: ```json { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 100 }, "scopes": { "default": [], "description": "Narrow the key to these scopes: `<group>:read`, `<group>:write`, `<group>:*`, `*:read`, or `billing:purchase` (groups: messages, conversations, contacts, templates, agents, webhooks, usage…). Empty (default): everything the key's role allows.", "maxItems": 64, "type": "array", "items": { "type": "string" } }, "expires_in_days": { "description": "Expire the key after this many days. Default: never.", "type": "integer", "minimum": 1, "maximum": 3650 } }, "required": [ "name" ] } ``` ### revoke_api_key Revoke API key. Writes. Group: keys. Scope: keys:write. Revoke an API key immediately; every backend, CLI, CI job or MCP host using it stops working. Owners and admins revoke any key; members their own. Input schema: ```json { "type": "object", "properties": { "key": { "type": "string", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" } }, "required": [ "key" ] } ``` ### get_pricing Get pricing. Read-only. Group: billing. Scope: billing:read. The plans (Free, Starter, Growth, Scale, Enterprise): price {monthly, yearly} in USD, included messages and AI replies a month, limits (live numbers, WhatsApp accounts, members, agents, history days; null is unlimited), features, overage rates and the default budget cap, a checkout_url per paid plan, and how_to_buy. Meta's own message fees are billed by Meta to the customer's WABA at cost and are not part of these prices. The same document as GET /api/v1/pricing; needs no credential (REST, the catalog MCP server at /api/mcp/catalog, `wabi pricing`). Call before recommending or buying a plan. Input schema: ```json { "type": "object", "properties": {} } ``` ### get_account Get account. Read-only. Group: billing. Scope: billing:read. Who this credential acts for and what it may use: the organization, your role in it (viewer, member, admin, owner) and whether you are a person, an OAuth client or an API key (acting_as, with the key's scopes), the plan in force (status, interval, renewal or end date, budget cap), usage this period against every limit (messages, AI replies, live numbers, members, agents) with what remains and the overage so far, and next_tier: what the next plan adds, its price and a checkout_url. Same data as GET /api/v1/account and `wabi account`. Call before a bulk send or when a human asks about plan or usage. Input schema: ```json { "type": "object", "properties": {} } ``` ### get_billing Get billing. Read-only. Group: billing. Scope: billing:read. This organization's plan, subscription status, renewal or end date, budget cap, whether a card is saved (so subscribe can charge it), and usage this period against the plan (messages, AI replies, numbers, members, agents, overage so far). Pass refresh: true right after a human finished a checkout to pull the latest state from Stripe. Input schema: ```json { "type": "object", "properties": { "refresh": { "default": false, "description": "Re-read the subscription from Stripe instead of the stored copy.", "type": "boolean" } } } ``` ### create_checkout_link Create checkout link. Writes. Needs the admin role. Group: billing. Scope: billing:write. A Stripe Checkout URL where a human pays for a plan; hand it to them (or open it in their browser). Works before signup: with no credential (REST, the catalog MCP server, `wabi upgrade` before login) the human pays first, then signs in with the same email and the plan attaches to their organization. Nothing is charged and the plan does not change until they complete it; then get_billing {refresh: true} shows the new plan. The link expires after 24 hours. If the organization already has a subscription, the URL is a Stripe page where they confirm the plan change instead. Returns {url, kind: checkout | plan_change, plan, interval, amount_cents, expires_at, session_id}. Never collect card details yourself. Input schema: ```json { "type": "object", "properties": { "plan": { "type": "string", "enum": [ "starter", "growth", "scale" ], "description": "starter, growth or scale. get_pricing lists what each includes. Enterprise is sold by email." }, "interval": { "default": "month", "description": "month, or year (two months free: 10x the monthly price).", "type": "string", "enum": [ "month", "year" ] }, "ref": { "description": "Optional attribution slug for the link (e.g. agent-cursor). Defaults to the surface: agent-mcp, agent-cli or agent-api.", "type": "string", "pattern": "^[a-z0-9][a-z0-9_-]{0,39}$" } }, "required": [ "plan" ] } ``` ### subscribe Subscribe. Writes. Needs the admin role. Group: billing. Scope: billing:write. Start or change this organization's paid plan in one call, charging the card already saved for the organization. Only after a human approved this plan and price: confirm must be true. Without a saved card (or if the card needs the human, e.g. 3-D Secure) nothing is charged and it returns status checkout_required with a checkout_url for a human to finish. A plan change is prorated and invoiced immediately. Returns {status: subscribed | changed | unchanged | checkout_required, billing}. Input schema: ```json { "type": "object", "properties": { "plan": { "type": "string", "enum": [ "starter", "growth", "scale" ], "description": "starter, growth or scale. get_pricing lists what each includes. Enterprise is sold by email." }, "interval": { "default": "month", "description": "month, or year (two months free: 10x the monthly price).", "type": "string", "enum": [ "month", "year" ] }, "confirm": { "type": "boolean", "const": true, "description": "Must be true: a human has seen the plan and price and asked you to buy it. Never set it on your own initiative." } }, "required": [ "plan", "confirm" ] } ``` ### cancel_subscription Cancel subscription. Writes. Needs the admin role. Group: billing. Scope: billing:write. Cancel the paid plan at the end of the current period (no refund; the plan stays until then, then the organization is on Free). Only when a human asked: confirm must be true. resume_subscription undoes it before the period ends. Input schema: ```json { "type": "object", "properties": { "confirm": { "type": "boolean", "const": true, "description": "Must be true: a human has seen the plan and price and asked you to buy it. Never set it on your own initiative." } }, "required": [ "confirm" ] } ``` ### resume_subscription Resume subscription. Writes. Needs the admin role. Group: billing. Scope: billing:write. Undo a cancellation that has not taken effect yet, so the plan renews as before. Nothing is charged until the normal renewal date. Input schema: ```json { "type": "object", "properties": {} } ``` ### set_budget_cap Set budget cap. Writes. Needs the admin role. Group: billing. Scope: billing:write. Set this organization's monthly overage budget cap in US dollars: usage past the plan's allowance is billed up to this amount, then non-critical sends (campaigns, bulk sends) stop until the next period. null restores the default (2x the plan price). Free has no overage and ignores it. Owners and admins; a human decision, so only when asked. Input schema: ```json { "type": "object", "properties": { "amount": { "anyOf": [ { "type": "number", "minimum": 0, "maximum": 1000000 }, { "type": "null" } ], "description": "Dollars per billing period, or null for the default." } }, "required": [ "amount" ] } ``` ### claim_checkout Claim checkout. Writes. Needs the admin role. Group: billing. Scope: billing:write. Attach a checkout paid before signup (create_checkout_link without a credential) to this organization, by its session_id (cs_…). Usually automatic: returning from Stripe, or signing up with the email used at checkout, claims it. Call it when a human paid first and the plan has not appeared. Returns {outcome: claimed | already_yours, billing}. Input schema: ```json { "type": "object", "properties": { "session_id": { "type": "string", "pattern": "^cs_(live|test)_[A-Za-z0-9]+$", "description": "The Checkout Session id create_checkout_link returned (cs_live_… or cs_test_…)." } }, "required": [ "session_id" ] } ``` ### create_billing_portal_link Create billing portal link. Writes. Needs the admin role. Group: billing. Scope: billing:write. A private Stripe page where a human downloads invoices, updates the card or billing details, or cancels. The link is for a human and expires after a few minutes of inactivity; never open it on your own. Input schema: ```json { "type": "object", "properties": {} } ``` ### get_billing_link Get billing link. Writes. Needs the admin role. Group: billing. Scope: billing:write. The Stripe customer portal for a human: invoices, card and billing details, plan change and cancellation. The same as create_billing_portal_link, under the name every Meter product uses (POST /api/v1/billing/portal, `wabi billing portal`). The link expires after a few minutes of inactivity; never open it on your own. Input schema: ```json { "type": "object", "properties": {} } ``` ### list_feedback List feature requests and issues. Read-only. Group: feedback. Scope: feedback:read. The Wabi feedback board: feature requests every customer can see, plus your own issue reports (and ones the team published), with status and upvotes, most upvoted first. Each item says whether your organization voted for it or filed it. Call it before request_feature or report_issue to find an existing item to upvote instead of filing a duplicate, or when the human asks what is planned. Works without a credential (public items only). Input schema: ```json { "type": "object", "properties": { "kind": { "default": "all", "description": "feature = feature requests, issue = bug reports (yours, and ones the team published).", "type": "string", "enum": [ "feature", "issue", "all" ] }, "status": { "default": "active", "description": "active = open, planned, in_progress.", "type": "string", "enum": [ "active", "all", "open", "planned", "in_progress", "done", "declined", "duplicate" ] }, "sort": { "default": "top", "description": "top = most upvoted, new = most recent, recent_activity = latest vote, comment or status change.", "type": "string", "enum": [ "top", "new", "recent_activity" ] }, "q": { "description": "Words to find in the title.", "type": "string", "maxLength": 100 }, "mine": { "default": false, "description": "Only items your organization filed.", "type": "boolean" }, "limit": { "default": 20, "description": "Page size, at most 100.", "type": "integer", "minimum": 1, "maximum": 100 }, "offset": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "cursor": { "description": "next_cursor from the previous page (same as offset).", "type": "string", "pattern": "^\\d+$" } } } ``` ### get_feedback Get a feature request or issue. Read-only. Group: feedback. Scope: feedback:read. One feedback item with its status, upvotes, comments (oldest first; official answers from the Wabi team are marked `official`) and its timeline of status changes with the team's notes. Your own issues include the context you attached. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The item's id from list_feedback, request_feature or report_issue (e.g. 42)." } }, "required": [ "id" ] } ``` ### list_my_feedback List my feedback and subscriptions. Read-only. Group: feedback. Scope: feedback:read. What your organization filed (every status), the items you get email about (filed, upvoted, commented, followed) with whether you muted them, your organization's review, and whether feedback email reaches you. Use it to follow up on earlier reports. Input schema: ```json { "type": "object", "properties": {} } ``` ### request_feature Request a feature. Writes. Group: feedback. Scope: feedback:write. Files a feature request on the Wabi board and upvotes it for your organization. Use it when the human wishes Wabi did something it cannot do yet (an integration, an op, a Meta feature, a dashboard view). It checks for duplicates first: an identical request gets your vote instead, and when likely matches exist nothing is filed and `similar` lists them (filed: false): upvote_feedback the match, or call again with force: true if yours is different. 10 new items per organization a day. The person behind the call gets a thank-you email and one at every status change. Feature requests and comments are public (never who wrote them beyond a first name): no API keys, message content or customer data. Input schema: ```json { "type": "object", "properties": { "title": { "type": "string", "minLength": 3, "maxLength": 140, "description": "What you want, in one line." }, "details": { "default": "", "description": "Why you need it and what you are building; concrete examples help it get planned.", "type": "string", "maxLength": 5000 }, "body": { "description": "Alias of details.", "type": "string", "maxLength": 5000 }, "force": { "default": false, "description": "File even when similar items exist.", "type": "boolean" } }, "required": [ "title" ] } ``` ### report_issue Report an issue. Writes. Group: feedback. Scope: feedback:write. Reports a problem with Wabi itself: a failing or wrong call (pass the request_id from the error or the X-Request-Id header, and the op), a message that was not delivered or delivered twice, a webhook that never arrived, a template that will not submit, an agent that misbehaved, a docs error. Attach context (account_id, conversation_id, message_id or wamid, template, Meta's error code, the error) so the team can reproduce it; ids are checked against your organization. Issues are private: only your organization and the Wabi team see them, and the team may publish a sanitized title so others can upvote it. Upvote a matching report from list_feedback (kind issue) instead of filing again; likely duplicates are returned in `similar` with filed: false unless force: true. 10 new items per organization a day. The reporter gets an email with what was captured and one at every status change. Input schema: ```json { "type": "object", "properties": { "title": { "type": "string", "minLength": 3, "maxLength": 140, "description": "What is wrong, in one line." }, "details": { "default": "", "description": "What you did, what you expected and what happened (status, error code, format).", "type": "string", "maxLength": 5000 }, "body": { "description": "Alias of details.", "type": "string", "maxLength": 5000 }, "request_id": { "description": "The request_id of the failing call (shortcut for context.request_id).", "type": "string", "maxLength": 128 }, "op": { "description": "The op the failing call used, e.g. send_message (shortcut for context.op).", "type": "string", "maxLength": 80 }, "context": { "description": "Wabi identifiers for what failed: request_id, op, account_id, conversation_id, message_id, wamid, template, meta_error_code, status, error_code, error, client. Never shown publicly; at most 128 KB.", "type": "object", "properties": { "request_id": { "description": "The request_id of the failing call (X-Request-Id header, or request_id in the error).", "type": "string", "pattern": "^[A-Za-z0-9._:-]{1,128}$" }, "op": { "description": "The op or endpoint that failed, e.g. send_message.", "type": "string", "pattern": "^[a-z][a-z0-9_]{1,79}$" }, "account_id": { "description": "The WhatsApp account (whatsapp_accounts.id) the problem is in; must be one of your organization's.", "type": "string", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }, "conversation_id": { "description": "The conversation involved; must be one of your organization's.", "type": "string", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }, "message_id": { "description": "The message involved (our id, not the wamid); must be one of your organization's.", "type": "string", "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" }, "wamid": { "description": "Meta's message id (wamid.…) when you have no message_id.", "type": "string", "maxLength": 200 }, "template": { "description": "The template name involved.", "type": "string", "maxLength": 512 }, "locale": { "description": "The language involved, e.g. pt_BR.", "type": "string", "pattern": "^[A-Za-z]{2,8}([-_][A-Za-z0-9]{1,8})*$" }, "meta_error_code": { "description": "Meta's error code, when Meta returned one (e.g. 131047).", "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "status": { "description": "The HTTP status the call returned.", "type": "integer", "minimum": 100, "maximum": 599 }, "error_code": { "description": "The error code, e.g. outside_service_window.", "type": "string", "maxLength": 100 }, "error": { "description": "The error message as the client saw it (no keys or customer strings).", "type": "string", "maxLength": 2000 }, "client": { "description": "Client and version, e.g. wabi-cli/0.5.0 or Claude Code.", "type": "string", "maxLength": 100 }, "version": { "type": "string", "maxLength": 50 }, "extra": { "description": "Anything else that helps reproduce it (no secrets).", "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": {} } }, "additionalProperties": false }, "force": { "default": false, "description": "File even when similar reports exist.", "type": "boolean" } }, "required": [ "title" ] } ``` ### upvote_feedback Upvote a feature request or issue. Writes. Group: feedback. Scope: feedback:write. Adds your organization's upvote to a feature request or issue ("me too"); votes decide what gets built and fixed first, and you get an email when its status changes. Idempotent: one vote per organization. A vote on a duplicate goes to the original. Returns the item with its new vote count. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The item's id from list_feedback, request_feature or report_issue (e.g. 42)." } }, "required": [ "id" ] } ``` ### remove_feedback_vote Remove an upvote. Writes. Group: feedback. Scope: feedback:write. Takes back your organization's upvote on a feature request or issue, and stops its emails unless you filed or commented on it. Returns the item with its new vote count. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The item's id from list_feedback, request_feature or report_issue (e.g. 42)." } }, "required": [ "id" ] } ``` ### comment_on_feedback Comment on a feature request or issue. Writes. Group: feedback. Scope: feedback:write. Adds context to an existing feature request or issue: your use case, another failing request, a workaround. Prefer it (with upvote_feedback) over filing a near-duplicate. You then get emails about the item; its author gets a daily digest of new comments. 50 comments per organization a day. Feature requests and comments are public (never who wrote them beyond a first name): no API keys, message content or customer data. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The item's id from list_feedback, request_feature or report_issue (e.g. 42)." }, "body": { "type": "string", "minLength": 1, "maxLength": 5000, "description": "The comment." } }, "required": [ "id", "body" ] } ``` ### set_feedback_subscription Start or stop feedback emails. Writes. Group: feedback. Scope: feedback:write. With id: start or stop emails about one feature request or issue. Without id: turn every feedback email (thank-yous, status changes, replies) on or off for the person behind this credential. Lifecycle and product emails are separate. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The item's id from list_feedback, request_feature or report_issue (e.g. 42)." }, "subscribed": { "type": "boolean", "description": "true to get emails, false to stop them." } }, "required": [ "subscribed" ] } ``` ### submit_review Post or edit a review. Writes. Group: feedback. Scope: feedback:write. Posts your organization's public review of Wabi (1–5 stars, what you like, what you don't, what you use it for), or edits it (once an hour; earlier versions are kept). Ask the human for the rating and words; never invent a review or post one they did not approve. It publishes on /reviews right away unless an automated check holds it for a quick look (new accounts, links, profanity). marketing_consent: true lets Meter quote it. The reviewer gets a thank-you email and one when the team replies. Input schema: ```json { "type": "object", "properties": { "rating": { "type": "integer", "minimum": 1, "maximum": 5, "description": "1 (poor) to 5 (excellent)." }, "title": { "default": "", "description": "One-line summary.", "type": "string", "maxLength": 120 }, "body": { "default": "", "description": "The review.", "type": "string", "maxLength": 2000 }, "likes": { "default": "", "description": "What works well.", "type": "string", "maxLength": 1000 }, "dislikes": { "default": "", "description": "What should be better.", "type": "string", "maxLength": 1000 }, "use_case": { "default": "", "description": "What you use Wabi for.", "type": "string", "maxLength": 200 }, "display_name": { "description": "Name shown with the review; defaults to your first name.", "type": "string", "maxLength": 60 }, "display_company": { "description": "Company shown with the review, if any.", "type": "string", "maxLength": 80 }, "marketing_consent": { "default": false, "description": "May Meter quote this review on the site and in marketing.", "type": "boolean" } }, "required": [ "rating" ] } ``` ### list_reviews List reviews. Read-only. Group: feedback. Scope: feedback:read. Published reviews of Wabi with the aggregate (average rating, count and distribution) and the team's responses. Works without a credential. Input schema: ```json { "type": "object", "properties": { "sort": { "default": "newest", "description": "helpful = most detailed first.", "type": "string", "enum": [ "newest", "helpful", "highest", "lowest" ] }, "rating": { "description": "Only reviews with this rating.", "type": "integer", "minimum": 1, "maximum": 5 }, "status": { "default": "published", "description": "Wabi team only: held or hidden reviews too.", "type": "string", "enum": [ "published", "pending", "hidden", "all" ] }, "limit": { "default": 20, "type": "integer", "minimum": 1, "maximum": 100 }, "offset": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 } } } ``` ### update_feedback Update feedback (staff). Writes. Group: feedback. Scope: feedback:write. Wabi team only. Moves an item through open → planned → in_progress → done, or declined, or duplicate (with duplicate_of: votes and subscribers move to the original), with a note every subscriber gets by email. Also publishes or hides an issue (visibility, with a sanitized public_title) and links the GitHub issue or PR (external_ref). Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The item's id from list_feedback, request_feature or report_issue (e.g. 42)." }, "status": { "type": "string", "enum": [ "open", "planned", "in_progress", "done", "declined", "duplicate" ] }, "note": { "description": "Why, or what shipped and how to use it; emailed with the status change.", "type": "string", "maxLength": 5000 }, "duplicate_of": { "description": "The original, with status duplicate.", "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "external_ref": { "description": "GitHub issue or PR URL; empty clears it.", "anyOf": [ { "type": "string", "maxLength": 500, "format": "uri" }, { "type": "string", "const": "" } ] }, "visibility": { "type": "string", "enum": [ "public", "private" ] }, "public_title": { "description": "Sanitized title others see for a published issue.", "type": "string", "maxLength": 140 } }, "required": [ "id" ] } ``` ### respond_to_feedback Respond officially (staff). Writes. Group: feedback. Scope: feedback:write. Wabi team only. Posts an official response on an item (emailed to every subscriber), optionally changing its status in the same call. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The item's id from list_feedback, request_feature or report_issue (e.g. 42)." }, "body": { "type": "string", "minLength": 1, "maxLength": 5000, "description": "The response, signed as the Wabi team." }, "status": { "type": "string", "enum": [ "open", "planned", "in_progress", "done", "declined", "duplicate" ] }, "note": { "type": "string", "maxLength": 5000 }, "duplicate_of": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "external_ref": { "type": "string", "maxLength": 500, "format": "uri" } }, "required": [ "id", "body" ] } ``` ### respond_to_review Respond to or moderate a review (staff). Writes. Group: feedback. Scope: feedback:write. Wabi team only. Replies publicly under a review (the reviewer gets an email), and/or hides or publishes it with a reason. Never hide a review for being negative. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The review id from list_reviews." }, "response": { "type": "string", "minLength": 1, "maxLength": 2000 }, "status": { "type": "string", "enum": [ "published", "hidden", "pending" ] }, "reason": { "description": "Why it is hidden; kept internal.", "type": "string", "maxLength": 300 } }, "required": [ "id" ] } ``` ### get_profile Get my profile. Read-only. Group: organization. Scope: organization:read. Your own settings, the same in every organization: `locale`, the language the dashboard and every email to you are written in (one of en), or null when you never chose one (the dashboard then follows the browser, emails are English). With an API key, the key creator's profile. Input schema: ```json { "type": "object", "properties": {} } ``` ### update_profile Update my profile. Writes. Group: organization. Scope: organization:write. Changes your own settings: `locale` (en) is the language of the dashboard and of every email Wabi sends you; null forgets it. The dashboard's language picker calls this. Returns the profile. Input schema: ```json { "type": "object", "properties": { "locale": { "anyOf": [ { "type": "string", "enum": [ "en" ] }, { "type": "null" } ], "description": "Your language: en, or null for none." } }, "required": [ "locale" ] } ``` ### get_sandbox Get the sandbox. Read-only. Group: sandbox. Scope: sandbox:read. This organization's sandbox: a simulated WhatsApp number every organization gets, for trying messaging, templates, the inbox, AI agents and webhooks without Meta. Creates it on first call. Returns the sandbox account (use account.id wherever an account id is asked for), its phone number (phone_number.id is the sender id for sends), the test recipients you can message, the failure recipient, and an explanation of how the simulator behaves. Call it before sending sandbox messages. Input schema: ```json { "type": "object", "properties": {} } ``` ### simulate_inbound_message Simulate an inbound message. Writes. Group: sandbox. Scope: sandbox:write. Make a sandbox test customer send a WhatsApp text to this organization's sandbox number, as if they typed it on their phone. It arrives through the same webhook pipeline as real Meta traffic (inbox, AI agents, automations, your webhooks) within seconds, and opens that customer's 24-hour window so free-form replies are allowed. Use it to test an agent or a flow without a phone. Returns the raw provider event id and the simulated message's wamid. Input schema: ```json { "type": "object", "properties": { "from": { "default": "15550100001", "description": "Test recipient WhatsApp id sending the message (get_sandbox → test_recipients). Default 15550100001.", "type": "string", "enum": [ "15550100000", "15550100001", "15550100002", "15550100003", "15550100004", "15550100005" ] }, "text": { "type": "string", "minLength": 1, "maxLength": 4096, "description": "The message text." }, "name": { "description": "The customer's WhatsApp profile name. Default: the test recipient's name.", "type": "string", "minLength": 1, "maxLength": 100 } }, "required": [ "text" ] } ``` ### list_events List events. Read-only. Group: events. Scope: events:read. The organization's event log, newest first: every platform event (message.received, message.sent, message.delivered, message.read, message.failed…) with its payload, subject, source and time. Filter by type (exact, or a prefix ending in a dot such as "message.") and subject. The same events customer webhooks deliver; use it to debug a webhook or to catch up after downtime (pass after_id to read forward). Input schema: ```json { "type": "object", "properties": { "type": { "description": "An event type, or a prefix ending in \".\" (e.g. \"message.\").", "type": "string", "maxLength": 100 }, "subject_type": { "description": "message, conversation, template, agent_session…", "type": "string", "maxLength": 50 }, "subject_id": { "type": "string", "maxLength": 200 }, "after_id": { "description": "Only events with a larger id, oldest first: to read forward from the last id you saw.", "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "limit": { "default": 50, "description": "Page size, at most 200.", "type": "integer", "minimum": 1, "maximum": 200 }, "offset": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 } } } ``` ### get_event Get event. Read-only. Group: events. Scope: events:read. One event from the organization's event log by id, with its payload and how each internal consumer handled it (done, failed with the error, dead after its retries). Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 } }, "required": [ "id" ] } ```