# wtsp.dev > WhatsApp data for AI agents. Send a phone number, learn whether it is on WhatsApp, or get back its WhatsApp profile, business details, profile photo or carrier. Every request is paid on its own, in USDC on Base with x402 or in stablecoins on Tempo with MPP: no sign-up, no API key. Base URL: https://wtsp.dev Every endpoint is a GET taking one query parameter, `phone`: Phone number with its country code, e.g. +14155552671. The + is optional; spaces, dashes and a leading 00 are fine. ## Endpoints - [GET /v1/exists](https://wtsp.dev/v1/exists?phone=14155552671): 0.004 USDC per request. Whether a number is on WhatsApp: a plain yes or no. - [GET /v1/profile](https://wtsp.dev/v1/profile?phone=14155552671): 0.006 USDC per request. Registration, account type, about text and the full business profile. - [GET /v1/profile/full](https://wtsp.dev/v1/profile/full?phone=14155552671): 0.01 USDC per request. Everything in /v1/profile plus a permanent profile-photo URL. - [GET /v1/carrier](https://wtsp.dev/v1/carrier?phone=447400123456): 0.004 USDC per request. Carrier, line type, country, region and time zones of any number. - [GET /v1/status](https://wtsp.dev/v1/status): free. Whether WhatsApp lookups are being answered right now. ## Paying with x402 1. Call the endpoint. You get `402 Payment Required`; the `PAYMENT-REQUIRED` header (base64 JSON) names the amount, the token (USDC on Base) and the address to pay. 2. Sign a USDC transfer authorization (EIP-3009) for exactly that amount and repeat the request with it in the `PAYMENT-SIGNATURE` header. No gas or ETH is needed. 3. You get `200` with the data. The `PAYMENT-RESPONSE` header carries the settlement transaction. Any x402 client does all three steps: - `npx wtsp`: the wtsp CLI. Creates a wallet, pays, prints JSON. Setup guide: https://wtsp.dev/skill.md - `@x402/fetch` (TypeScript) or the `x402` Python package, with a funded Base wallet. - AgentCash and other x402 wallets, which discover the endpoints from https://wtsp.dev/openapi.json. ## Paying with MPP instead Every 402 also carries MPP challenges (the Machine Payments Protocol) in `WWW-Authenticate`: the same price, paid in a stablecoin on Tempo (OUSD or USDC.e). Any MPP client pays them, e.g. `npx mppx https://wtsp.dev/v1/profile?phone=14155552671` or `npx wtsp mode mpp`. The payment is validated first and only broadcast once the answer is ready, so a failed lookup costs nothing here either. The receipt comes back in the `Payment-Receipt` header. ## Paying with an API key instead Prepaid credit suits callers who would rather not sign a payment per request. Buy it once, in whole USDC, with `POST /v1/credits?amount=10` (paid with x402) or at https://wtsp.dev/#keys, and you get a key that is shown once. Then send it with every request: Authorization: Bearer wtsp_… Same prices; failed lookups are refunded to the key. Each response carries the credit left in the `wtsp-credits-remaining` header, and `GET /v1/credits` with the key returns the balance. A key out of credit gets a 402 with code `insufficient_credits`. POST /v1/credits with the key tops it up. The CLI uses a key when `WTSP_API_KEY` is set. ## What is charged - 200: The answer, “not on WhatsApp” included. Charged. - 400: Malformed number, rejected before any payment is asked for. Not charged. - 402: Payment required, or the payment sent was invalid. Not charged. - 404: Carrier lookup only: no carrier on record for the number. Not charged. - 503: WhatsApp did not answer. The payment is cancelled, never settled. Not charged. Errors are JSON: `{"error": {"code": "...", "message": "..."}}`. ## Response: /v1/exists The cheapest check: whether the number is on WhatsApp, and nothing else. - `phone`: string. The number looked up, in E.164. - `hasWhatsApp`: boolean. Whether the number is a registered WhatsApp account. `false` is a definitive answer, not a failure. - `checkedAt`: string. When this answer was fetched from WhatsApp (ISO 8601). Example: ```json { "phone": "+14155552671", "hasWhatsApp": true, "checkedAt": "2026-10-09T18:24:03.000Z" } ``` ## Response: /v1/profile and /v1/profile/full A number that is on WhatsApp returns the fields below; one that is not returns only `phone`, `hasWhatsApp: false` and `checkedAt`. `profile.picUrl` and `profile.picHash` exist on /v1/profile/full only. - `phone`: string. The number looked up, in E.164. - `hasWhatsApp`: true. The number is a registered WhatsApp account. - `accountType`: string. `business` when WhatsApp published a business profile. - `identity`: object - `identity.countryCode`: string | null. Country WhatsApp reports for the account (ISO 3166-1 alpha-2), not one guessed from the prefix. - `identity.lid`: string | null. WhatsApp's internal account id. Numbers that share one account share it. - `profile`: object - `profile.about`: string | null. The "about" (status) text. - `profile.aboutRestricted`: boolean. True when an about text exists but privacy settings hide it, so `about` is empty. - `profile.picUrl`: string | null. Permanent URL of the profile photo; null when there is none or privacy settings hide it. - `profile.picHash`: string | null. Content hash of the photo. Unchanged hash, unchanged photo. - `devices` (optional): object. Absent on the rare occasion WhatsApp sends no device list. - `devices.count`: number. Linked devices including the phone itself, so 1 means phone only. - `devices.hosted`: boolean. Served by the WhatsApp Business Platform (Cloud API) rather than a phone: a strong real-business signal. - `business`: object | null. Business profile; null for personal accounts. - `business.displayName`: string | null. Public business name. Personal accounts publish no name at all. - `business.description`: string | null - `business.categories`: string[]. Every category the business picked; often more than one. - `business.email`: string | null - `business.websites`: string[] - `business.address`: string | null - `business.latitude`: number | null - `business.longitude`: number | null - `business.timezone`: string | null. IANA zone the opening hours are in. - `business.hours`: object[]. Opening hours per day. - `business.hours[].day`: string. Day of week, e.g. `mon`. - `business.hours[].mode`: string. `specific_hours`, `open_24h` or `appointment_only` (times are null then). - `business.hours[].openTime`: string | null. Opening time, HH:MM. - `business.hours[].closeTime`: string | null. Closing time, HH:MM. - `business.memberSince`: string | null. When the business joined, as WhatsApp words it: "Joined in January, 2026". - `business.memberSinceTs`: number | null. The same moment as a unix timestamp (seconds). - `business.businessType`: string | null. `smb` for a small business, or an enterprise tier. - `business.verifiedLevel`: string | null. Meta verification tier. - `business.cartEnabled`: boolean. Whether the account sells through a WhatsApp cart. - `business.commerceExperience`: string | null - `business.automatedType`: string | null. Set when WhatsApp flags the account as automated (a bot). - `business.linkedFacebookPage`: object | null. Linked Facebook page and its follower count. - `business.linkedFacebookPage.id`: string - `business.linkedFacebookPage.name`: string | null - `business.linkedFacebookPage.likes`: number | null - `checkedAt`: string. When this answer was fetched from WhatsApp (ISO 8601). Example: ```json { "phone": "+14155552671", "hasWhatsApp": true, "accountType": "business", "identity": { "countryCode": "US", "lid": "258166877098174@lid" }, "profile": { "about": "Fresh groceries, delivered daily 🍋", "aboutRestricted": false, "picUrl": "https://wtsp.dev/photos/258166877098174/tkphavoFaBWOiNjZo_B5L-uG8Bw4BjhRDNPgDs-1vp4.jpg", "picHash": "tkphavoFaBWOiNjZo/B5L+uG8Bw4BjhRDNPgDs+1vp4=" }, "devices": { "count": 2, "hosted": false }, "business": { "displayName": "Fresh Market SF", "description": "Neighbourhood grocery with same-day delivery across San Francisco.", "categories": [ "Grocery Store", "Food delivery" ], "email": "hello@freshmarket.example", "websites": [ "https://freshmarket.example" ], "address": "123 Market St, San Francisco, CA 94103", "latitude": 37.7749, "longitude": -122.4194, "timezone": "America/Los_Angeles", "hours": [ { "day": "mon", "mode": "specific_hours", "openTime": "08:00", "closeTime": "20:00" }, { "day": "sat", "mode": "specific_hours", "openTime": "09:00", "closeTime": "18:00" }, { "day": "sun", "mode": "appointment_only", "openTime": null, "closeTime": null } ], "memberSince": "Joined in March, 2021", "memberSinceTs": 1615420800, "businessType": "smb", "verifiedLevel": "unknown", "cartEnabled": true, "commerceExperience": "none", "automatedType": "unknown", "linkedFacebookPage": { "id": "100064523310987", "name": "Fresh Market SF", "likes": 1840 } }, "checkedAt": "2026-10-09T18:24:03.000Z" } ``` ## Response: /v1/carrier - `phone`: string. The number looked up, in E.164. - `carrier`: string. Network the number range was issued to. Ported numbers keep their original carrier. - `lineType`: string. `mobile`, `fixed line`, `fixed line or mobile`, `voip`, `toll free`… - `isMobile`: boolean | null. Null where a country hands mobiles and landlines out of one shared range. - `country`: string. ISO 3166-1 alpha-2 country of the number. - `countryName`: string - `flag`: string - `countryCallingCode`: string - `location`: string | null. Town or region the number was issued in, where published. - `timezones`: string[] - `international`: string. The number in international format. - `national`: string. The number as dialled inside its country. Example: ```json { "phone": "+447400123456", "carrier": "Three", "lineType": "mobile", "isMobile": true, "country": "GB", "countryName": "United Kingdom", "flag": "🇬🇧", "countryCallingCode": "+44", "location": null, "timezones": [ "Europe/London" ], "international": "+44 7400 123456", "national": "07400 123456" } ``` ## Good to know - Personal accounts publish no name. Only business accounts have `business.displayName`. - An about text hidden by privacy settings comes back empty with `profile.aboutRestricted: true`. - Carrier data comes from the numbering plan: ported numbers report their original carrier, and some countries (the US and Canada among them) publish none. ## More - OpenAPI: https://wtsp.dev/openapi.json - Agent skill (CLI setup): https://wtsp.dev/skill.md