API guide
Call the company API with a bearer key. This page does not ask you to sign in, and it does not show a secret. Creating, rotating, and revoking keys stays on the developer portal after you log in.
The integrator reference opens on this site (Redoc). The full platform Swagger stays in Admin.
A tool result that fails its schema is JSON-RPC error -32603, "Internal error". An unknown tool is not_found, "Unknown tool." Error codes: validation_failed 400, unauthorized 401, forbidden 403, not_found 404, conflict 409, idempotency_conflict 409, gone 410, rate_limited 429, upstream_error 5xx, session_revoke_unavailable 503, internal_error 500.
Every MCP request, including initialize and tools/list, uses the same 403 rules as REST. A pending, disabled, or platform company is checked first, so it wins over a creator problem. That case, and a super-admin creator, returns 403 forbidden: "This account cannot perform the action." In an active company, a deleted, demoted, moved, or missing creator returns 403 forbidden: "This API key's creator is no longer an active admin of this company. Create a new key from an active admin." An Origin header is not accepted. MCP then returns 403 forbidden: "Origin is not allowed". A companyId or tenantId in the request body, or a companyId in the URL path, for another company returns 403 forbidden: "That company is not yours". A facility outside this company returns 403 forbidden: "That facility is not in this company". Creating a key returns 403 forbidden "Platform companies do not have API keys" or "Company admin only". A tenantId or companyId tool argument is ignored. The company comes from the key. GET, PUT, PATCH, and DELETE return 405.
Bearer API key
Send this header on company API calls. The prefix identifies the key. The rest is the secret, and it is shown once.
Authorization: Bearer wh_live_xxxxxxxxxxxx
keyId is the id on the developer portal. It is not the secret. Each key has a per-minute ceiling. Over that ceiling the API returns 429 and a Retry-After header with the seconds left in that minute.
Create a customer
POST /v1/customers with a company API key must include Idempotency-Key. Send the same value again with the same body to retry that create. The API returns the original customer. A different body with the same key is rejected. A dashboard session omits this header.
Idempotency-Key: <a unique value for this create>
Webhook HMAC
Inbound and outbound HMAC secrets are separate, and they are not API keys. Prefixes look like wh_hook_in_… and wh_hook_out_…. One live secret per direction. The secret is shown once on the developer portal.
X-WalletMe-Signature: sha256=<lowercase hex HMAC-SHA256 of the raw body>
Sign that header over the raw body. API keys do not authenticate webhooks. Rotate leaves the previous secret valid until its revoke time. Revoke ends it.
Keys
A company keeps one live key. Rotate issues a new secret and leaves the previous key valid until its revoke time. That overlap starts at 24 hours. Revoke ends a key immediately. Revoked keys stay listed as an id and a prefix.
Create, rotate, and revoke after you log in on your company subdomain (for example company.sandbox…). This page does not show a secret and does not link into the workspace.
Connect an AI tool
This server is for MCP clients that can send an Authorization header (for example Cursor).
Call POST https://api.walletmeinc.com/mcp. Stateless MCP Streamable HTTP. Only the exact path /mcp is accepted. /mcp/, /MCP, and //mcp return 404. POST /mcp accepts one JSON-RPC message and counts as one API-key rate-limit unit. JSON-RPC batches are not accepted. Send Content-Type: application/json and Accept: application/json, text/event-stream.
POST https://api.walletmeinc.com/mcp
Authorization is a company API key from Developer → API key after you log in on your company subdomain. It is the same per-company key, with the same approval, rate limit, revoke, and rotate. The key can write data. Create a dedicated key for the MCP client. A session JWT or session cookie is not a credential and returns 401.
Authorization: Bearer wh_live_xxxxxxxxxxxx
The sample uses a placeholder. This page does not show a secret.
Cursor (~/.cursor/mcp.json)
{
"mcpServers": {
"walletme": {
"url": "https://api.walletmeinc.com/mcp",
"headers": {
"Authorization": "Bearer wh_live_xxxxxxxxxxxx"
}
}
}
}Keep this file out of git, or use an environment variable if your client supports one.
Error codes list the JSON-RPC failures and the 403 messages.
create_customer, get_customer, list_customers and get_pass_status: Customer records leave out email, display name, phone, and notes, and also name, to-address, password, and password hash. Company, group, facility, and template names stay.
create_customercreates or updates a customer for the company on the API key, the same as POST /v1/customers. Opens the wallet and queues the pass email. Does not take a password. idempotency_key is required for an API key and is the Idempotency-Key: the same key and body replay the first customer, and a different body is idempotency_conflict. tenantId and companyId are ignored.get_customerreads one customer in the company on the API key, the same as GET /v1/customers/{customerId}/view. Another company is not_found. tenantId and companyId are ignored.list_customerslists customers for the company on the API key, the same as GET /v1/customers. cursor is opaque and company-scoped. limit defaults to 50 and is at most 100. tenantId and companyId are ignored.list_card_templatesopens the card studio for the company on the API key, the same as GET /v1/card-studio. Omit facilityId for the company-wide card. Returns the open document, saved versions, scopes, and presets. Nothing is written.get_card_templatereads one saved card design version for the company on the API key, the same as GET /v1/card-studio/versions/{version}. version is the design version. Another company is not_found.get_pass_linkissues the wallet pass and returns a one-time download link, the same as POST /v1/customers/{customerId}/wallet-cards. This renders the pass and stores a file. It is not a read. The link expires in at most 15 minutes. The result is url, expiresAt, and the card ids and versions. Another company is not_found.get_pass_statusreads how far this customer pass has reached, the same as GET /v1/customers/{customerId}/card-history. stage is created, issued, emailed, or on_phone, plus the timeline. Notes are removed from pass history. The design name (look.name, templateName) is returned. Another company is not_found.get_loyalty_balancereads a loyalty member for the company on the API key, the same as GET /v1/loyalty/members/{memberId}. Returns pointsBalance and one ledger page. A disabled program, or another company member, is not_found. cursor and limit match that route.record_loyalty_tapRecord an earn or redeem for the company on the API key, the same as POST /v1/loyalty/taps. idempotency_key is required. The same key and body replay the first tap. A disabled program is not_found. points is an integer from 1 to 100000. occurredAt must be within the last 366 days and no more than 24 hours ahead. Overflow or insufficient points is conflict.get_field_contractreads the variable names, types, and QR string format for the open card of the company on the API key. Nothing is written. The values come from the card document and the public pass and loyalty field definitions. Omit facilityId for the company-wide card.
idempotency_key is required for an API key on create_customer and record_loyalty_tap. The same key and body replay, and a different body is idempotency_conflict.