Docs / WallaB Developer Platform
API reference (v1)
The complete v1 reference: base URL, versioning, rate limits, errors, pagination, and every endpoint.
The complete reference for the WallaB Developer Platform v1 — a read-only, key-authenticated data API for your own systems and AI agents. New here? Start with the overview and quickstart, then read authentication for keys and scopes.
Base URL & versioning
All endpoints live under /api/external/v1/. The absolute base URL is:
https://wallab.ai/api/external/v1The version is in the path (/v1). We add fields and endpoints without a version bump — your client must ignore unknown JSON fields and not assume a response has only the fields it knows. A breaking change would ship as a new version (/v2) alongside /v1.
Deprecation policy. If a version is ever retired, we announce it in advance and, during the wind-down, send a Deprecation header and a Sunset header (an HTTP-date after which the version stops responding) on every response, per RFC 8594. There is no sunset scheduled for v1.
The unversioned /api/external/entitlements path is kept as a permanent back-compat alias of /v1/entitlements; new integrations should use /v1.
Authentication
Send your key as a bearer token:
Authorization: Bearer wlb_your_key_hereThe platform is available on the Growth plan or higher. On Free or Starter every request is refused with 403 { "success": false, "message": "Developer API access requires the Growth plan or higher." }. Any missing, malformed, unknown, or revoked key gets the identical 401 { "success": false, "message": "Unauthorized" } — the API never reveals which check failed. Full detail on keys, scopes, and rotation is on the Authentication page.
Rate limits
Requests are limited per API key, on a rolling one-minute window, by plan:
- Growth — 60 requests / minute.
- Pro — 300 requests / minute.
- Enterprise Plus — uncapped.
Over the limit returns 429 with a Retry-After header (seconds to wait). Design your integration to cache results briefly and back off on Retry-After rather than hammering a retry loop.
Scopes
Each key carries read-only scopes; each endpoint requires exactly one. A key that lacks the required scope is refused with 403.
read:entitlements— customer entitlement status.read:subscriptions— subscription records and detail.read:metrics— aggregate retention/revenue metrics and cancel-save outcomes.read:plans— selling-plan definitions.
Errors
Every non-2xx response is the same envelope:
{ "success": false, "message": "A human-readable explanation." }Status codes:
- 400 Bad Request — invalid input (a bad
customerId,limit,cursor, or filter value). - 401 Unauthorized — any authentication failure (uniform body; the reason is never distinguishable).
- 403 Forbidden — the plan lacks developer API access, or the key is missing the endpoint's required scope.
- 404 Not Found — the subscription-detail endpoint only, for an id that isn't yours (identical to a missing id — no existence oracle).
- 429 Too Many Requests — rate limited; wait the
Retry-Afterseconds. - 500 Internal Server Error — something went wrong on our side; safe to retry later.
Pagination
List endpoints (/subscriptions and /cancellations) are cursor-paginated:
limit— page size, 1–100, default 50.cursor— pass the previous response'snextCursorto fetch the next page. It is opaque; treat it as a token, not a value to construct.- Each response carries a
nextCursor— a string when more rows remain, ornullon the last page. An invalidcursororlimitreturns400.
Machine-readable specs
Point your tooling and AI agents at the generated contract — it is built from the same source as this page, so it can never drift from the live endpoints:
- OpenAPI 3.1:
/api/external/v1/openapi.json - LLM index:
/llms.txt·/llms-full.txt
Every endpoint below is also exposed as a read-only tool on the MCP server, for AI assistants that speak the Model Context Protocol.
GET /api/external/v1/subscriptions
Scope: read:subscriptions. List the shop's subscriptions.
Query params: limit, cursor (see pagination), and an optional status filter — one of active, paused, cancelled, expired, failed.
curl -sS "https://wallab.ai/api/external/v1/subscriptions?limit=50&status=active" \
-H "Authorization: Bearer wlb_your_key_here"const url = new URL("https://wallab.ai/api/external/v1/subscriptions");
url.searchParams.set("limit", "50");
url.searchParams.set("status", "active");
const res = await fetch(url, {
headers: { Authorization: "Bearer wlb_your_key_here" },
});
const { subscriptions, nextCursor } = await res.json();{
"subscriptions": [
{
"subscriptionId": 501,
"customerId": 1042,
"status": "active",
"planId": 7,
"planName": "Coffee Club Monthly",
"planType": "physical",
"currentPrice": 24.50,
"nextBillingDate": "2026-08-01T00:00:00.000Z",
"createdAt": "2026-01-01T00:00:00.000Z"
}
],
"nextCursor": "NTAx"
}currentPrice is the subscription's per-cycle value (line prices × quantity). customerId is an opaque, shop-scoped internal id — never PII.
Errors: 400 (bad limit, cursor, or status), 401, 403, 429.
GET /api/external/v1/subscriptions/{id}
Scope: read:subscriptions. One subscription's detail plus its line items.
{id} is the numeric subscription id. A missing, non-numeric, or another shop's id all return the identical 404 { "success": false, "message": "Subscription not found." } — the endpoint can't be used to probe which ids exist. Line items carry the shop's own catalog fields (product/variant ids and titles) — that's your data, not customer data.
curl -sS "https://wallab.ai/api/external/v1/subscriptions/501" \
-H "Authorization: Bearer wlb_your_key_here"const res = await fetch(
"https://wallab.ai/api/external/v1/subscriptions/501",
{ headers: { Authorization: "Bearer wlb_your_key_here" } },
);
if (res.status === 404) throw new Error("No such subscription");
const { subscription } = await res.json();{
"subscription": {
"subscriptionId": 501,
"customerId": 1042,
"status": "active",
"planId": 7,
"planName": "Coffee Club Monthly",
"planType": "physical",
"currentPrice": 24.50,
"nextBillingDate": "2026-08-01T00:00:00.000Z",
"createdAt": "2026-01-01T00:00:00.000Z",
"lineItems": [
{
"lineId": 9,
"productId": 100,
"productTitle": "Dark Roast",
"variantId": 200,
"variantTitle": "12 oz",
"quantity": 2,
"currentPrice": 12.50
}
]
}
}Errors: 401, 403, 404 (unknown/foreign/malformed id), 429.
GET /api/external/v1/metrics/summary
Scope: read:metrics. The dashboard KPIs, programmatically — the exact same figures the admin dashboard shows (same engine, so they can never disagree). Aggregate numbers only; no per-customer rows.
curl -sS "https://wallab.ai/api/external/v1/metrics/summary" \
-H "Authorization: Bearer wlb_your_key_here"const res = await fetch(
"https://wallab.ai/api/external/v1/metrics/summary",
{ headers: { Authorization: "Bearer wlb_your_key_here" } },
);
const { summary } = await res.json();{
"summary": {
"activeSubscribers": 128,
"pausedSubscribers": 12,
"mrr": 3200.00,
"revenue30d": 2980.00,
"newSubscribers30d": 18,
"cancelled30d": 4,
"churnRate30d": 0.031,
"saveRate": 0.62,
"cancelSessions30d": 21,
"dunningActive": 3,
"recoveryRate": 0.75,
"failedPayments30d": 2,
"avgSubscriptionValue": 25.00,
"upcomingRenewals7d": 30,
"upcomingRenewalsValue7d": 740.00,
"generatedAt": "2026-07-08T00:00:00.000Z"
}
}Money values are in the shop's currency; rates (churnRate30d, saveRate, recoveryRate) are fractions in [0, 1].
Errors: 401, 403, 429.
GET /api/external/v1/plans
Scope: read:plans. The shop's selling plans (your subscribe-and-save cadences), with a pricing summary, active flag, and product count. Returns the full set (plans per shop are bounded — no pagination).
curl -sS "https://wallab.ai/api/external/v1/plans" \
-H "Authorization: Bearer wlb_your_key_here"const res = await fetch("https://wallab.ai/api/external/v1/plans", {
headers: { Authorization: "Bearer wlb_your_key_here" },
});
const { plans } = await res.json();{
"plans": [
{
"id": 7,
"name": "Monthly",
"groupName": "Coffee Club",
"intervalUnit": "month",
"intervalCount": 1,
"active": true,
"productCount": 4,
"pricing": {
"discountType": "percentage",
"discountValue": 15,
"summary": "15% off"
}
}
]
}Errors: 401, 403, 429.
GET /api/external/v1/cancellations
Scope: read:metrics (cancel/save outcomes are the same retention analytics that scope already governs — no separate scope to grant). The cancel-save concierge outcomes, cursor-paginated and opaque (no customer PII).
Query params: limit, cursor (see pagination), and an optional outcome filter — one of saved, cancelled, abandoned.
curl -sS "https://wallab.ai/api/external/v1/cancellations?limit=50&outcome=saved" \
-H "Authorization: Bearer wlb_your_key_here"const url = new URL("https://wallab.ai/api/external/v1/cancellations");
url.searchParams.set("limit", "50");
url.searchParams.set("outcome", "saved");
const res = await fetch(url, {
headers: { Authorization: "Bearer wlb_your_key_here" },
});
const { cancellations, nextCursor } = await res.json();{
"cancellations": [
{
"sessionId": 88,
"detectedReason": "too expensive",
"outcome": "saved",
"offerType": "offer_discount",
"createdAt": "2026-06-01T00:00:00.000Z"
}
],
"nextCursor": null
}outcome is saved, cancelled, abandoned, or null for an in-progress session; offerType is the concierge offer the shopper was shown (offer_discount, pause, swap, skip_next) or null if none.
Errors: 400 (bad limit, cursor, or outcome), 401, 403, 429.