Products
Products endpoints of the Zanfia Workspace API, with schemas and code samples.
Create, edit, publish, and price products. All routes require a Authorization: Bearer header — see Authentication.
List products
/productsAuthorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Response Body
application/json
application/json
curl -X GET "https://example.com/products"{ "count": 0, "products": [ { "id": "string", "name": "string", "slug": "string", "productType": "course", "status": "draft", "isFree": true, "createdAt": "string" } ]}{ "error": "string"}Create a product
/productsAuthorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
curl -X POST "https://example.com/products" \ -H "Content-Type: application/json" \ -d '{ "name": "string", "productType": "course" }'{ "id": "string", "name": "string", "slug": "string", "productType": "course", "status": "draft", "isFree": true, "createdAt": "string", "defaultCheckoutId": "string"}{ "error": "string"}Product detail
/products/{productId}Authorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Path Parameters
Product id
Response Body
application/json
application/json
curl -X GET "https://example.com/products/string"{ "id": "string", "name": "string", "slug": "string", "productType": "course", "status": "draft", "isFree": true, "createdAt": "string", "description": "string", "clientCount": 0, "plans": [ { "id": "string", "name": "string", "description": "string", "active": true, "isDefault": true, "order": 0, "includedProducts": [ { "productId": "string", "productName": "string", "productType": "course" } ], "communityRoleGrants": { "property1": [ "string" ], "property2": [ "string" ] }, "createdAt": "string", "updatedAt": "string" } ]}{ "error": "string"}Update product info
/products/{productId}Authorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Path Parameters
Product id
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Partial update of a product's display info. Omitted fields are left unchanged.
Response Body
application/json
application/json
curl -X PATCH "https://example.com/products/string" \ -H "Content-Type: application/json" \ -d '{}'{ "productId": "string"}{ "error": "string"}Archive a product
/products/{productId}/archiveAuthorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Path Parameters
Product id
Response Body
application/json
application/json
curl -X POST "https://example.com/products/string/archive"{ "productId": "string", "status": "draft"}{ "error": "string"}Publish or unpublish a product
/products/{productId}/statusAuthorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Path Parameters
Product id
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
curl -X POST "https://example.com/products/string/status" \ -H "Content-Type: application/json" \ -d '{ "status": "draft" }'{ "productId": "string", "status": "draft"}{ "error": "string"}Appointment settings
/products/{productId}/appointmentAuthorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Path Parameters
Product id
Response Body
application/json
application/json
curl -X GET "https://example.com/products/string/appointment"{ "productType": "consultation", "consultation": { "bookingMethod": "native", "bookingUrl": "string", "duration": 0, "location": "string", "locationDetails": "string", "confirmationMessage": "string", "showOnSuccessPage": true, "availability": [ { "dayOfWeek": 0, "startTime": "string", "endTime": "string" } ], "exceptions": [ "string" ], "bufferMinutes": 0, "maxBookingsPerDay": 0, "maxBookingsPerWeek": 0, "minNoticeHours": 0, "bookingWindowDays": 0, "timezone": "string" }, "service": { "deliverables": [ "string" ], "deliveryTime": { "value": 0, "unit": "days" }, "revisionRounds": 0, "briefMethod": "native", "briefUrl": "string", "showOnSuccessPage": true, "briefFields": [ { "id": "string", "label": "string", "type": "shortText", "required": true, "options": "string" } ] }, "event": { "type": "online", "location": "string", "startsAt": "string", "endsAt": "string", "capacity": 0, "showOnSuccessPage": true }}{ "error": "string"}Update appointment settings
/products/{productId}/appointmentAuthorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Path Parameters
Product id
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Upsert an appointment product's content config. Provide EXACTLY ONE of the three sub-objects, and it must match the product's own type (else 400/409).
Response Body
application/json
application/json
curl -X PATCH "https://example.com/products/string/appointment" \ -H "Content-Type: application/json" \ -d '{}'{ "updated": true}{ "error": "string"}After-purchase settings
/products/{productId}/after-purchaseAuthorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Path Parameters
Product id
Response Body
application/json
application/json
curl -X GET "https://example.com/products/string/after-purchase"{ "settings": { "thankYou": { "type": "defaultPage", "redirectUrl": "string", "custom": { "eyebrow": "string", "headline": "string", "messageHtml": "string", "messageMarkdown": "string", "ctaLabel": "string", "ctaUrl": "string", "showOrderSummary": true } }, "emails": { "sendPurchaseConfirmationToBuyer": true, "sendPurchaseNotificationToOwner": true, "sendFailedPaymentEmailToBuyer": true, "sendFailedPaymentEmailToOwner": true }, "purchaseEmail": { "purchase": { "subject": "string", "headline": "string", "messageHtml": "string", "messageMarkdown": "string" }, "free": { "subject": "string", "headline": "string", "messageHtml": "string", "messageMarkdown": "string" }, "trial": { "subject": "string", "headline": "string", "messageHtml": "string", "messageMarkdown": "string" } }, "renewalReminderEmail": { "subject": "string", "headline": "string", "messageHtml": "string", "messageMarkdown": "string" } }}{ "error": "string"}Update after-purchase settings
/products/{productId}/after-purchaseAuthorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Path Parameters
Product id
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Partial write of the product's shared after-purchase defaults. Omitted sections keep their stored value (the defaults are configured progressively). Written sections are mirrored onto every non-overriding offer of the product.
Response Body
application/json
application/json
curl -X PATCH "https://example.com/products/string/after-purchase" \ -H "Content-Type: application/json" \ -d '{}'{ "updated": true}{ "error": "string"}Replace product images
/products/{productId}/imagesAuthorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Path Parameters
Product id
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Full replace of a product's cover image array. Each entry must be an https URL ALREADY hosted (upload to the Media Library first — POST /media — and use its returned url; this route only writes the product's images array and best-effort syncs them to the linked Stripe Product). Certificate / generated cover fields are not editable here.
Response Body
application/json
application/json
curl -X PUT "https://example.com/products/string/images" \ -H "Content-Type: application/json" \ -d '{ "images": [ "string" ] }'{ "images": [ "string" ]}{ "error": "string"}List product prices
/products/{productId}/pricesAuthorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Path Parameters
Product id
Response Body
application/json
application/json
curl -X GET "https://example.com/products/string/prices"{ "count": 0, "prices": [ { "id": "string", "productId": "string", "amountCents": 0, "currency": "string", "type": "one_time", "active": true, "status": "available", "gateways": [ "string" ], "label": "string" } ]}{ "error": "string"}Add a product price
/products/{productId}/pricesAuthorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Path Parameters
Product id
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Create a one-time, single-currency paid price on the product's default checkout (creating the checkout if none exists). The Stripe price is created server-side; if no payment gateway is connected yet the price is stored unregistered (empty gateways) and registered when the offer is published.
Response Body
application/json
application/json
curl -X POST "https://example.com/products/string/prices" \ -H "Content-Type: application/json" \ -d '{ "amountCents": 0, "currency": "string" }'{ "id": "string", "productId": "string", "amountCents": 0, "currency": "string", "type": "one_time", "active": true, "status": "available", "gateways": [ "string" ], "label": "string"}{ "error": "string"}Replace plans & bundling
/products/{productId}/plansAuthorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Path Parameters
Product id
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Full replace of a product's plans[] (plans & bundling). Mirrors the internal UpdateProductPlansRequestDTO: the caller sends the complete desired state; the server generates ids for new plans, owns timestamps, and enforces the invariants (unique ids, at most one default, no self-include, one level of bundling both directions, and a delete guard when active checkout prices still reference a removed plan).
Response Body
application/json
application/json
curl -X PUT "https://example.com/products/string/plans" \ -H "Content-Type: application/json" \ -d '{ "plans": [ { "name": "string", "active": true, "includedProducts": [ { "productId": "string", "productName": "string" } ] } ] }'{ "productId": "string", "plans": [ { "id": "string", "name": "string", "description": "string", "active": true, "isDefault": true, "order": 0, "includedProducts": [ { "productId": "string", "productName": "string", "productType": "course" } ], "communityRoleGrants": { "property1": [ "string" ], "property2": [ "string" ] }, "createdAt": "string", "updatedAt": "string" } ]}{ "error": "string"}Count clients per plan
/products/{productId}/plans/count-clientsAuthorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Path Parameters
Product id
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Count the product's active customers whose EFFECTIVE plan is planId (grant match, falling back to the default plan — the same resolution the access checks use). Omit planId to size a not-yet-saved first/default plan: every active grant counts.
Response Body
application/json
application/json
curl -X POST "https://example.com/products/string/plans/count-clients" \ -H "Content-Type: application/json" \ -d '{}'{ "count": 0}{ "error": "string"}Backfill plan community members
/products/{productId}/plans/backfill-community-membersAuthorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Path Parameters
Product id
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Eagerly create community-member docs for every active customer whose effective plan includes communityProductId — sends membership grants, so run it deliberately (typically right after adding a community to a plan). Idempotent: existing members are left untouched.
Response Body
application/json
application/json
curl -X POST "https://example.com/products/string/plans/backfill-community-members" \ -H "Content-Type: application/json" \ -d '{ "communityProductId": "string" }'{ "added": 0, "alreadyMembers": 0, "skipped": 0}{ "error": "string"}Change a client's plan
/products/{productId}/clients/planAuthorization
bearerAuth API key from Dashboard → Integrations → API, MCP and CLI.
In: header
Path Parameters
Product id
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Move a client's grant to a different plan on a multi-plan product. Billing-neutral: only grant.planId moves; community membership is reconciled server-side as part of the same request.
Response Body
application/json
application/json
curl -X POST "https://example.com/products/string/clients/plan" \ -H "Content-Type: application/json" \ -d '{ "clientId": "string", "planId": "string" }'{ "clientId": "string", "planId": "string", "membership": { "added": 0, "alreadyMembers": 0, "skipped": 0, "removed": 0 }}{ "error": "string"}Was this article helpful?

