REST API reference
Every op, its input schema and a curl example, generated from the ops registry.
Every action in Wabi is an op. Call one with POST https://getwabi.dev/api/v1/{op} and its input as a JSON body. Read-only ops also answer GET with the input as URL-encoded JSON in ?input=. This page is generated from the same registry that serves the API, so it lists exactly what the API accepts.
- Auth:
Authorization: Bearer wabi_…(orX-Api-Key). A scoped key can call only the ops its scopes cover. With an OAuth or session token, pick an organization withx-wabi-org: <slug>. - Errors are RFC 9457
application/problem+jsonwith acodeandrequest_id: see Errors. - Machine-readable: OpenAPI 3.1, the op catalog and llms-full.txt.
curl -X POST https://getwabi.dev/api/v1/get_account \ -H "Authorization: Bearer wabi_…"get_pricing and create_checkout_link answer without a credential (also as GET /api/v1/pricing). Groups with no ops yet are still being built; GET /api/v1 always lists exactly what is live.
All ops
Sandbox
The simulated number every organization gets, for trying everything before Meta.
get_sandbox
readGet the sandbox · POST /api/v1/get_sandbox · scope sandbox:read or GET …?input=
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.
curl -X POST https://getwabi.dev/api/v1/get_sandbox \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi get_sandbox{ "type": "object", "properties": {}}Simulate an inbound message · POST /api/v1/simulate_inbound_message · 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.
curl -X POST https://getwabi.dev/api/v1/simulate_inbound_message \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"text":"Hi, where is my order?"}'npx @meterapp/wabi simulate_inbound_message --json '{"text":"Hi, where is my order?"}'{ "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" ]}Events
The organization's event stream.
list_events
readList events · POST /api/v1/list_events · scope events:read or GET …?input=
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).
curl -X POST https://getwabi.dev/api/v1/list_events \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi list_events{ "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
readGet event · POST /api/v1/get_event · scope events:read or GET …?input=
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).
curl -X POST https://getwabi.dev/api/v1/get_event \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"id":1}'npx @meterapp/wabi get_event --json '{"id":1}'{ "type": "object", "properties": { "id": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 } }, "required": [ "id" ]}Organization
Settings, onboarding and the activity log.
get_organization
readGet organization · POST /api/v1/get_organization · scope organization:read or GET …?input=
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.
curl -X POST https://getwabi.dev/api/v1/get_organization \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi get_organization{ "type": "object", "properties": {}}Update organization · POST /api/v1/update_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.
curl -X POST https://getwabi.dev/api/v1/update_organization \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi update_organization{ "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
readList activity · POST /api/v1/list_activity · scope organization:read or GET …?input=
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.
curl -X POST https://getwabi.dev/api/v1/list_activity \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi list_activity{ "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
readGet onboarding checklist · POST /api/v1/get_onboarding · scope organization:read or GET …?input=
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.
curl -X POST https://getwabi.dev/api/v1/get_onboarding \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi get_onboarding{ "type": "object", "properties": {}}Referral signups (staff) · POST /api/v1/get_referral_stats · scope organization:read or GET …?input=
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.
curl -X POST https://getwabi.dev/api/v1/get_referral_stats \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi get_referral_stats{ "type": "object", "properties": { "days": { "default": 30, "description": "Window for signups_since. Default 30.", "type": "integer", "minimum": 1, "maximum": 3650 } }}get_profile
readGet my profile · POST /api/v1/get_profile · scope organization:read or GET …?input=
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.
curl -X POST https://getwabi.dev/api/v1/get_profile \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi get_profile{ "type": "object", "properties": {}}update_profile
writeUpdate my profile · POST /api/v1/update_profile · 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.
curl -X POST https://getwabi.dev/api/v1/update_profile \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"locale":"en"}'npx @meterapp/wabi update_profile --json '{"locale":"en"}'{ "type": "object", "properties": { "locale": { "anyOf": [ { "type": "string", "enum": [ "en" ] }, { "type": "null" } ], "description": "Your language: en, or null for none." } }, "required": [ "locale" ]}Members and sharing
Members, teams, resource sharing and the audit log.
list_members
readList members · POST /api/v1/list_members · scope members:read or GET …?input=
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).
curl -X POST https://getwabi.dev/api/v1/list_members \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi list_members{ "type": "object", "properties": {}}List invites · POST /api/v1/list_invites · scope members:read or GET …?input=
Pending invitations to this organization (email, role, teams, who invited, when it expires). Owners and admins.
curl -X POST https://getwabi.dev/api/v1/list_invites \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi list_invites{ "type": "object", "properties": { "include_closed": { "default": false, "description": "Also list accepted, revoked and expired invites.", "type": "boolean" } }}list_teams
readList teams · POST /api/v1/list_teams · scope members:read or GET …?input=
Teams in this organization with their members (maintainer or member) and the resources (WhatsApp accounts, AI agents) shared with each team.
curl -X POST https://getwabi.dev/api/v1/list_teams \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi list_teams{ "type": "object", "properties": {}}create_team
writeCreate team · POST /api/v1/create_team · 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.
curl -X POST https://getwabi.dev/api/v1/create_team \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"name":"Support"}'npx @meterapp/wabi create_team --json '{"name":"Support"}'{ "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
writeUpdate team · POST /api/v1/update_team · scope members:write
Rename a team or change its description. Its maintainers, owners and admins.
curl -X POST https://getwabi.dev/api/v1/update_team \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"team":"support"}'npx @meterapp/wabi update_team --json '{"team":"support"}'{ "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
writeDelete team · POST /api/v1/delete_team · scope members:write
Delete a team. Its members stay in the organization but lose what was shared with the team.
curl -X POST https://getwabi.dev/api/v1/delete_team \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"team":"support"}'npx @meterapp/wabi delete_team --json '{"team":"support"}'{ "type": "object", "properties": { "team": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Team slug or id." } }, "required": [ "team" ]}set_team_member
writeAdd to team · POST /api/v1/set_team_member · 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.
curl -X POST https://getwabi.dev/api/v1/set_team_member \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"team":"support","user":"user"}'npx @meterapp/wabi set_team_member --json '{"team":"support","user":"user"}'{ "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
writeRemove from team · POST /api/v1/remove_team_member · 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.
curl -X POST https://getwabi.dev/api/v1/remove_team_member \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"team":"support","user":"user"}'npx @meterapp/wabi remove_team_member --json '{"team":"support","user":"user"}'{ "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 · POST /api/v1/list_resource_access · scope members:read or GET …?input=
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.
curl -X POST https://getwabi.dev/api/v1/list_resource_access \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"resource_type":"whatsapp_account","resource_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'npx @meterapp/wabi list_resource_access --json '{"resource_type":"whatsapp_account","resource_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'{ "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 · POST /api/v1/set_resource_visibility · 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.
curl -X POST https://getwabi.dev/api/v1/set_resource_visibility \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"resource_type":"whatsapp_account","resource_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10","visibility":"org"}'npx @meterapp/wabi set_resource_visibility --json '{"resource_type":"whatsapp_account","resource_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10","visibility":"org"}'{ "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" ]}List audit log · POST /api/v1/list_audit_log · scope members:read or GET …?input=
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.
curl -X POST https://getwabi.dev/api/v1/list_audit_log \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi list_audit_log{ "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 } }}API keys
Organization keys for your backend, the CLI, MCP and CI.
list_api_keys
readList API keys · POST /api/v1/list_api_keys · scope keys:read or GET …?input=
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.
curl -X POST https://getwabi.dev/api/v1/list_api_keys \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi list_api_keys{ "type": "object", "properties": {}}create_api_key
writeCreate API key · POST /api/v1/create_api_key · 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.
curl -X POST https://getwabi.dev/api/v1/create_api_key \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"name":"Support"}'npx @meterapp/wabi create_api_key --json '{"name":"Support"}'{ "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
writeRevoke API key · POST /api/v1/revoke_api_key · 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.
curl -X POST https://getwabi.dev/api/v1/revoke_api_key \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"key":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'npx @meterapp/wabi revoke_api_key --json '{"key":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'{ "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" ]}Billing
Plans, prices, checkout links, the budget cap and the subscription.
Get pricing · POST /api/v1/get_pricing · scope billing:read or GET …?input=
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.
curl -X POST https://getwabi.dev/api/v1/get_pricing \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi get_pricing{ "type": "object", "properties": {}}get_account
readGet account · POST /api/v1/get_account · scope billing:read or GET …?input=
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.
curl -X POST https://getwabi.dev/api/v1/get_account \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi get_account{ "type": "object", "properties": {}}get_billing
readGet billing · POST /api/v1/get_billing · scope billing:read or GET …?input=
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.
curl -X POST https://getwabi.dev/api/v1/get_billing \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi get_billing{ "type": "object", "properties": { "refresh": { "default": false, "description": "Re-read the subscription from Stripe instead of the stored copy.", "type": "boolean" } }}Create checkout link · POST /api/v1/create_checkout_link · 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.
curl -X POST https://getwabi.dev/api/v1/create_checkout_link \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"plan":"starter"}'npx @meterapp/wabi create_checkout_link --json '{"plan":"starter"}'{ "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 · POST /api/v1/subscribe · 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}.
curl -X POST https://getwabi.dev/api/v1/subscribe \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"plan":"starter","confirm":false}'npx @meterapp/wabi subscribe --json '{"plan":"starter","confirm":false}'{ "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 · POST /api/v1/cancel_subscription · 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.
curl -X POST https://getwabi.dev/api/v1/cancel_subscription \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"confirm":false}'npx @meterapp/wabi cancel_subscription --json '{"confirm":false}'{ "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 · POST /api/v1/resume_subscription · 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.
curl -X POST https://getwabi.dev/api/v1/resume_subscription \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi resume_subscription{ "type": "object", "properties": {}}Set budget cap · POST /api/v1/set_budget_cap · 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.
curl -X POST https://getwabi.dev/api/v1/set_budget_cap \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"amount":0}'npx @meterapp/wabi set_budget_cap --json '{"amount":0}'{ "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 · POST /api/v1/claim_checkout · 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}.
curl -X POST https://getwabi.dev/api/v1/claim_checkout \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"session_id":"session_id"}'npx @meterapp/wabi claim_checkout --json '{"session_id":"session_id"}'{ "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 · POST /api/v1/create_billing_portal_link · 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.
curl -X POST https://getwabi.dev/api/v1/create_billing_portal_link \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi create_billing_portal_link{ "type": "object", "properties": {}}Get billing link · POST /api/v1/get_billing_link · 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.
curl -X POST https://getwabi.dev/api/v1/get_billing_link \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi get_billing_link{ "type": "object", "properties": {}}Feedback
Request features, report issues and upvote what matters to you.
List feature requests and issues · POST /api/v1/list_feedback · scope feedback:read or GET …?input=
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).
curl -X POST https://getwabi.dev/api/v1/list_feedback \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi list_feedback{ "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 a feature request or issue · POST /api/v1/get_feedback · scope feedback:read or GET …?input=
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.
curl -X POST https://getwabi.dev/api/v1/get_feedback \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"id":1}'npx @meterapp/wabi get_feedback --json '{"id":1}'{ "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
readList my feedback and subscriptions · POST /api/v1/list_my_feedback · scope feedback:read or GET …?input=
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.
curl -X POST https://getwabi.dev/api/v1/list_my_feedback \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi list_my_feedback{ "type": "object", "properties": {}}request_feature
writeRequest a feature · POST /api/v1/request_feature · 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.
curl -X POST https://getwabi.dev/api/v1/request_feature \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"title":"Show template rejection reasons in the inbox"}'npx @meterapp/wabi request_feature --json '{"title":"Show template rejection reasons in the inbox"}'{ "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
writeReport an issue · POST /api/v1/report_issue · 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.
curl -X POST https://getwabi.dev/api/v1/report_issue \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"title":"Show template rejection reasons in the inbox"}'npx @meterapp/wabi report_issue --json '{"title":"Show template rejection reasons in the inbox"}'{ "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
writeUpvote a feature request or issue · POST /api/v1/upvote_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.
curl -X POST https://getwabi.dev/api/v1/upvote_feedback \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"id":1}'npx @meterapp/wabi upvote_feedback --json '{"id":1}'{ "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
writeRemove an upvote · POST /api/v1/remove_feedback_vote · 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.
curl -X POST https://getwabi.dev/api/v1/remove_feedback_vote \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"id":1}'npx @meterapp/wabi remove_feedback_vote --json '{"id":1}'{ "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
writeComment on a feature request or issue · POST /api/v1/comment_on_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.
curl -X POST https://getwabi.dev/api/v1/comment_on_feedback \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"id":1,"body":"It would save us a trip to Meta Business Manager."}'npx @meterapp/wabi comment_on_feedback --json '{"id":1,"body":"It would save us a trip to Meta Business Manager."}'{ "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" ]}Start or stop feedback emails · POST /api/v1/set_feedback_subscription · 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.
curl -X POST https://getwabi.dev/api/v1/set_feedback_subscription \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"subscribed":false}'npx @meterapp/wabi set_feedback_subscription --json '{"subscribed":false}'{ "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
writePost or edit a review · POST /api/v1/submit_review · 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.
curl -X POST https://getwabi.dev/api/v1/submit_review \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"rating":1}'npx @meterapp/wabi submit_review --json '{"rating":1}'{ "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 · POST /api/v1/list_reviews · scope feedback:read or GET …?input=
Published reviews of Wabi with the aggregate (average rating, count and distribution) and the team's responses. Works without a credential.
curl -X POST https://getwabi.dev/api/v1/list_reviews \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi list_reviews{ "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
writeUpdate feedback (staff) · POST /api/v1/update_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).
curl -X POST https://getwabi.dev/api/v1/update_feedback \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"id":1}'npx @meterapp/wabi update_feedback --json '{"id":1}'{ "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
writeRespond officially (staff) · POST /api/v1/respond_to_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.
curl -X POST https://getwabi.dev/api/v1/respond_to_feedback \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"id":1,"body":"It would save us a trip to Meta Business Manager."}'npx @meterapp/wabi respond_to_feedback --json '{"id":1,"body":"It would save us a trip to Meta Business Manager."}'{ "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
writeRespond to or moderate a review (staff) · POST /api/v1/respond_to_review · 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.
curl -X POST https://getwabi.dev/api/v1/respond_to_review \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"id":1}'npx @meterapp/wabi respond_to_review --json '{"id":1}'{ "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" ]}