Memberships
Este conteúdo não está disponível em sua língua ainda.
Memberships are recurring customer plans, a monthly or yearly subscription that can carry benefits like a shop-wide discount, included services per billing cycle, and priority handling. The API exposes the plan catalog, customer enrollments, a fast live-membership lookup (the same check the in-app checkout badge uses), and a way to mint a plan’s public signup link.
This resource is read-first by design: the money paths, enroll, charge, cancel, pause, card vaulting, are deliberately not exposed on the public API. The only write is the signup-link mint, which is inert: it returns the same public self-serve signup URL the staff UI shares.
The membership plan object
Section titled “The membership plan object”{ "object": "membership_plan", "id": 4, "name": "Priority Care", "description": "Priority bench time and 10% off repairs.", "amount": { "amount_cents": 1999, "amount": "19.99", "currency": "USD" }, "billing_interval": "month", "benefits": { "discount_pct": 10, "included_services": [ { "name": "Screen protector install", "qty": 1 } ], "priority": true }, "active": true, "created_at": "2026-04-02T15:00:00.000Z", "updated_at": "2026-06-20T10:12:00.000Z"}| Field | Type | Description |
|---|---|---|
id | integer | Unique plan ID |
name | string | Plan name |
description | string|null | Plan description |
amount | money | The recurring price per billing interval |
billing_interval | string | month or year |
benefits | object|null | The plan’s benefits definition, when one is configured (see below) |
active | boolean | false = archived (closed to new enrollments) |
created_at | string|null | ISO-8601 creation timestamp |
updated_at | string|null | ISO-8601 last-updated timestamp |
Benefit fields
Section titled “Benefit fields”| Field | Type | Description |
|---|---|---|
discount_pct | number|null | Shop-wide discount percent, 0–100 |
included_services | array | { name, qty } pairs, included uses per billing cycle |
priority | boolean | Members get priority handling |
The membership object
Section titled “The membership object”An enrollment of one customer in one plan.
{ "object": "membership", "id": 118, "plan_id": 4, "plan_name": "Priority Care", "customer_email": "jane.smith@example.com", "customer_name": "Jane Smith", "customer_phone": "555-867-5309", "status": "active", "started_at": "2026-05-01T14:00:00.000Z", "next_charge_at": "2026-08-01T14:00:00.000Z", "cancel_at_period_end": false, "canceled_at": null}| Field | Type | Description |
|---|---|---|
id | integer | Unique enrollment ID |
plan_id | integer | The plan enrolled in |
plan_name | string|null | Denormalized plan name |
customer_email | string|null | Customer’s email |
customer_name | string|null | Customer’s name |
customer_phone | string|null | Customer’s phone |
status | string | active, paused, canceled, past_due, pending_signature, or expired |
started_at | string|null | ISO-8601 enrollment start |
next_charge_at | string|null | The next billing-cycle boundary (the current period’s end) |
cancel_at_period_end | boolean | true when a cancel is scheduled for the cycle boundary |
canceled_at | string|null | ISO-8601 cancellation timestamp |
List membership plans
Section titled “List membership plans”GET /api/v1/memberships/plansReturns the plan catalog, cursor-paginated. Archived plans are hidden by default; pass include_archived=true to include them.
Scope required: memberships.read
Query parameters
Section titled “Query parameters”| Parameter | Type | Description |
|---|---|---|
include_archived | boolean | Include archived (inactive) plans (default: false) |
limit | integer | Page size, 1–100 (default: 25) |
cursor | string | Opaque cursor from a previous response’s next_cursor |
curl "https://app.benchkey.com/api/v1/memberships/plans" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Response
Section titled “Response”{ "object": "list", "data": [ { "object": "membership_plan", "id": 4, "name": "Priority Care" } ], "has_more": false, "next_cursor": null}Additional resource fields are omitted from this example.
Retrieve a membership plan
Section titled “Retrieve a membership plan”GET /api/v1/memberships/plans/:plan_idReturns a single plan.
Scope required: memberships.read
curl "https://app.benchkey.com/api/v1/memberships/plans/4" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Response
Section titled “Response”Returns the membership plan object. Returns 404 if no plan with that ID exists.
List memberships
Section titled “List memberships”GET /api/v1/membershipsReturns a cursor-paginated list of customer plan enrollments, newest first. Filter by customer email, plan, or status.
Scope required: memberships.read
Query parameters
Section titled “Query parameters”| Parameter | Type | Description |
|---|---|---|
customer | string | Filter to one customer’s memberships by email (exact, case-insensitive) |
plan_id | integer | Filter to enrollments of one plan |
status | string | Filter by status: active, paused, canceled, past_due, pending_signature, expired |
limit | integer | Page size, 1–100 (default: 25) |
cursor | string | Opaque cursor from a previous response’s next_cursor |
curl "https://app.benchkey.com/api/v1/memberships?status=active&limit=10" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const res = await fetch( "https://app.benchkey.com/api/v1/memberships?status=active&limit=10", { headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } });const { data, has_more, next_cursor } = await res.json();Response
Section titled “Response”{ "object": "list", "data": [ { "object": "membership", "id": 118, "status": "active" } ], "has_more": false, "next_cursor": null}Additional resource fields are omitted from this example.
See Pagination for how to page through results.
Retrieve a membership
Section titled “Retrieve a membership”GET /api/v1/memberships/:idReturns one customer plan enrollment by ID.
Scope required: memberships.read
curl "https://app.benchkey.com/api/v1/memberships/118" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Response
Section titled “Response”Returns the membership object. Returns 404 if no membership with that ID exists.
Look up a customer’s live membership
Section titled “Look up a customer’s live membership”GET /api/v1/memberships/lookupAnswers “is this customer a live member?” for one email. Mirrors the in-app checkout badge: only live statuses (active, past_due, paused) count, and when a customer holds several live enrollments the healthiest one wins (active over past_due over paused).
Scope required: memberships.read
Query parameters
Section titled “Query parameters”| Parameter | Required | Type | Description |
|---|---|---|---|
customer | yes | string | The customer’s email address |
curl "https://app.benchkey.com/api/v1/memberships/lookup?customer=jane.smith@example.com" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Response
Section titled “Response”{ "object": "membership_lookup", "customer_email": "jane.smith@example.com", "member": true, "status": "active", "plan_name": "Priority Care"}| Field | Type | Description |
|---|---|---|
customer_email | string | The email queried |
member | boolean | true when the customer has a live membership |
status | string|null | active, past_due, or paused, null when member is false |
plan_name | string|null | The live plan’s name, or null |
Mint a plan’s public signup link
Section titled “Mint a plan’s public signup link”POST /api/v1/memberships/plans/:plan_id/signup-linkMints (or returns, a plan keeps its token forever, so links in the wild never rot) the plan’s public self-serve signup URL. Idempotent: repeated calls return the same link.
Scope required: memberships.write
curl -X POST "https://app.benchkey.com/api/v1/memberships/plans/4/signup-link" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const res = await fetch( "https://app.benchkey.com/api/v1/memberships/plans/4/signup-link", { method: "POST", headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" }, });const { url } = await res.json();Response
Section titled “Response”{ "object": "membership_signup_link", "plan_id": 4, "token": "3f2a9c81d4e07b5a6c9d2e8f1a0b3c4d5e6f7a8b9c0d1e2f", "url": "https://app.benchkey.com/plan-signup/3f2a9c81d4e07b5a6c9d2e8f1a0b3c4d5e6f7a8b9c0d1e2f"}| Field | Type | Description |
|---|---|---|
plan_id | integer | The plan the link signs customers up for |
token | string | The public signup token (stable for the plan’s lifetime) |
url | string | The public self-serve signup URL to share |
Error codes
Section titled “Error codes”| HTTP status | Code | Meaning |
|---|---|---|
400 | invalid_query | A query parameter was supplied as an array or object instead of a single scalar value, or an invalid status/plan_id filter |
400 | invalid_cursor | The pagination cursor is malformed |
400 | missing_field | The required customer query parameter is absent on lookup |
404 | not_found | No membership or membership plan with that ID exists for this tenant |
422 | create_failed | The signup link could not be minted |
403 | insufficient_scope | API key lacks memberships.read or memberships.write |
See Errors for the full error envelope format.