Plans and purchases
See the plans Wingspan offers an Account, check eligibility, and buy a plan with a signed consent using the V3 API.
A Plan is something an Account can buy from Wingspan. A Purchase is the act of buying one. Creating a Purchase also creates the Subscription that carries the Plan, in one step. See Subscriptions for what happens next.
Prerequisites
revenue.catalog:readto read Plans andrevenue.purchases:readorrevenue.purchases:writefor Purchases.- A signed-in Person session to create a Purchase. The Purchase records who signed the terms, so an API key or service account is refused.
X-Wingspan-Accountset to the Account that buys.
Endpoints
| Purpose | Method and path |
|---|---|
| List Plans | GET /v3/platform/plans |
| Get a Plan | GET /v3/platform/plans/{planId} |
| Create a Purchase | POST /v3/platform/purchases |
| List Purchases | GET /v3/platform/purchases |
| Get a Purchase | GET /v3/platform/purchases/{purchaseId} |
| Cancel a Purchase | POST /v3/platform/purchases/{purchaseId}/cancel |
| Renew a Purchase | POST /v3/platform/purchases/{purchaseId}/renew |
| List catalog items | GET /v3/platform/purchase-catalog |
| Get a catalog item | GET /v3/platform/purchase-catalog/{skuId} |
Find a Plan the Account can buy
Plans are personalized. Each one reports isEligible and, when it is false, a list of blockers.
| Parameter | Values |
|---|---|
filter[type][eq] | Service, Benefit |
filter[isEligible][eq] | true, false |
filter[acquisition.mode][eq] | Direct, DomainService |
sort | name, status, type or isEligible, each asc or desc |
expand | products adds the Plan's products. Without it they are left out of the list. |
curl -g "https://api.wingspan.app/v3/platform/plans?filter[isEligible][eq]=true&filter[type][eq]=Service&expand=products" \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "X-Wingspan-Account: Nw7kQ2pLx9RtVb3mHc5dZa"A Plan has id, name, description, type, status (Active or ClosedToNewBusiness), prices, termOptions, requiredConsents, acquisition, isEligible and blockers. Each price has skuId, currency, unit (Quantity or Percentage), amount or rateBp, validFrom, and termOptionId. A term option has an id and an interval of Monthly, Annual, or MultiYear with a value.
Blocker code is one of PlanNotPurchasable, PlanClosedToNewBusiness, RequirementNotFulfilled, ConcurrentSubscriptionNotAllowed or EnrollmentWindowClosed. RequirementNotFulfilled also carries a requirementId. See Requirements and eligibility.
acquisition.mode tells you where the Plan is bought. A DomainService Plan is bought through its own service flow, and acquisition.platformPurchaseSupported says whether you can buy it here.
Create a Purchase
Send Idempotency-Key. It is required.
| Field | Required | Notes |
|---|---|---|
planId | Yes | A Plan the Account is eligible for. |
consents | Yes | One entry for each item in the Plan's requiredConsents: termsRef, version, documentSha256, signedAt, and optional consentProvenance. |
termOptionId | No | One of the Plan's termOptions. |
instrumentSelection.fundingInstrumentId | No | The funding instrument to bill. |
fulfillmentData | No | Data the Plan's fulfillment needs. |
You cannot send a price. Wingspan reads it from the Plan. The request does not take a payment.
curl -X POST https://api.wingspan.app/v3/platform/purchases \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "X-Wingspan-Account: Nw7kQ2pLx9RtVb3mHc5dZa" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"planId": "Zc6vKm2PxQ8tNb4wRd7LhE",
"termOptionId": "Tm3yWq9HcB5xKd2nVp8JaF",
"consents": [
{
"termsRef": "terms/service-agreement",
"version": "2026-01",
"documentSha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"signedAt": "2026-11-02T16:00:00Z"
}
]
}'The values are illustrative. Copy termsRef, version and documentSha256 from the Plan's requiredConsents.
The response is 201 with the Purchase: id, planId, planVersion, status (Pending, Completed, Failed), subscriptionId, pricing, consents, subscription (id, status, activationGates) and events. pricing has currency, lineItems, total, grossTotal, taxTotal and taxDeterminationPending. While the Subscription is still being provisioned, the body adds provisioningPending: true. Read the Purchase again later.
The Idempotent-Replayed header says whether the response is a replay. Replaying a key returns 201 with the original response, or 200 with the current Purchase when the original is older.
Errors
| Status | Cause |
|---|---|
400 | Idempotency-Key is missing. |
401 | The Person session is not valid. |
403 | You called with an API key or service account. Consent needs a Person session. |
409 | The Plan cannot be bought now. The problem lists any eligibility blockers, such as ConcurrentSubscriptionNotAllowed. |
422 | A field is invalid. The problem lists errors by field. |
503 | The purchase service did not return a valid result. Retry later with the same Idempotency-Key. |
Cancel and renew
Cancel a Purchase and Renew a Purchase take no body. To end a service plan, use Cancel a subscription, which is described in Subscriptions. List Purchases takes page[size] and page[token].
Catalog items
List catalog items and Get a catalog item return SKUs: id, name, description, category (Insurance, Plan or AddOn), price, currency, interval (OneTime, Monthly or Annual) and isAvailable. A missing SKU returns 404. Use Plans, not catalog items, to decide what an Account can buy.
Webhook events
Purchase lifecycle events are published as webhooks. See Webhooks and events to subscribe.
Updated 12 minutes ago