Workspace API

Products

Products endpoints of the Zanfia Workspace API, with schemas and code samples.

10 min readLast updated Oct 8, 2026

Create, edit, publish, and price products. All routes require a Authorization: Bearer header — see Authentication.

List products

GET/products

Authorization

bearerAuth
AuthorizationBearer <token>

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",  "message": "string"}

Create a product

POST/products

Authorization

bearerAuth
AuthorizationBearer <token>

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",  "message": "string"}

Product detail

GET/products/{productId}

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

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"    }  ],  "storefrontVisibility": "public"}
{  "error": "string",  "message": "string"}

Update product info

PATCH/products/{productId}

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

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",  "message": "string"}

Delete product

DELETE/products/{productId}

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

Product id

Response Body

application/json

application/json

curl -X DELETE "https://example.com/products/string"
{  "productId": "string",  "status": "draft",  "purgeAfter": 0,  "runId": "string",  "activeGrants": 0,  "cancellableSubscriptions": 0}
{  "error": "string",  "message": "string"}

Archive a product

POST/products/{productId}/archive

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

Product id

Response Body

application/json

application/json

curl -X POST "https://example.com/products/string/archive"
{  "productId": "string",  "status": "draft"}
{  "error": "string",  "message": "string"}

Publish or unpublish a product

POST/products/{productId}/status

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

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",  "message": "string"}

Appointment settings

GET/products/{productId}/appointment

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

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,    "sessionsPerPurchase": 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,    "liveRoomId": "string"  }}
{  "error": "string",  "message": "string"}

Update appointment settings

PATCH/products/{productId}/appointment

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

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",  "message": "string"}

After-purchase settings

GET/products/{productId}/after-purchase

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

Product id

Response Body

application/json

application/json

curl -X GET "https://example.com/products/string/after-purchase"
{  "settings": {    "thankYou": {      "type": "defaultPage",      "redirectUrl": "string",      "appendEmailSha256": true,      "custom": {        "eyebrow": "string",        "headline": "string",        "messageHtml": "string",        "messageMarkdown": "string",        "ctaLabel": "string",        "ctaUrl": "string",        "showOrderSummary": true,        "nextOffer": {          "kind": "checkout",          "checkoutId": "string",          "productId": "string",          "headline": "string",          "description": "string",          "ctaLabel": "string"        }      }    },    "emails": {      "sendPurchaseConfirmationToBuyer": true,      "sendPurchaseNotificationToOwner": true,      "sendFailedPaymentEmailToBuyer": true,      "sendFailedPaymentEmailToOwner": true    },    "emailOverrides": {      "property1": {        "enabled": true,        "subject": {          "en": "string",          "pl": "string"        },        "headline": {          "en": "string",          "pl": "string"        },        "layout": [          {            "id": "string",            "type": "text",            "text": {              "en": "string",              "pl": "string"            }          }        ],        "body": {          "en": "string",          "pl": "string"        },        "variants": {          "property1": {},          "property2": {}        },        "attachments": [          {            "kind": "media",            "mediaId": "string",            "name": "string",            "mimeType": "string",            "size": 0,            "storagePath": "string",            "url": "string"          }        ]      },      "property2": {        "enabled": true,        "subject": {          "en": "string",          "pl": "string"        },        "headline": {          "en": "string",          "pl": "string"        },        "layout": [          {            "id": "string",            "type": "text",            "text": {              "en": "string",              "pl": "string"            }          }        ],        "body": {          "en": "string",          "pl": "string"        },        "variants": {          "property1": {},          "property2": {}        },        "attachments": [          {            "kind": "media",            "mediaId": "string",            "name": "string",            "mimeType": "string",            "size": 0,            "storagePath": "string",            "url": "string"          }        ]      }    }  }}
{  "error": "string",  "message": "string"}

Update after-purchase settings

PATCH/products/{productId}/after-purchase

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

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",  "message": "string"}

Replace product images

PUT/products/{productId}/images

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

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",  "message": "string"}

List product prices

GET/products/{productId}/prices

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

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",  "message": "string"}

Add a product price

POST/products/{productId}/prices

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

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",  "message": "string"}

Replace plans & bundling

PUT/products/{productId}/plans

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

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). Checkout prices still linked to a removed plan are unlinked in the same call (they resolve to the default plan); upgrade offers touching it are deactivated.

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",  "message": "string"}

Count clients per plan

POST/products/{productId}/plans/count-clients

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

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",  "message": "string"}

Backfill plan community members

POST/products/{productId}/plans/backfill-community-members

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

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",  "message": "string"}

Change a client's plan

POST/products/{productId}/clients/plan

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

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",  "message": "string"}

Rename download file

PATCH/products/{productId}/downloads/files/{uid}

Authorization

bearerAuth
AuthorizationBearer <token>

API key from Dashboard → Integrations → API, MCP and CLI.

In: header

Path Parameters

productId*string

Product id

uid*string

Download file uid

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

New label of one attached file; it is also the buyer's download filename (max 255 characters).

Response Body

application/json

application/json

curl -X PATCH "https://example.com/products/string/downloads/files/string" \  -H "Content-Type: application/json" \  -d '{    "name": "string"  }'
{  "file": {    "url": "string",    "name": "string",    "uid": "string",    "mediaId": "string",    "size": 0  },  "files": [    {      "url": "string",      "name": "string",      "uid": "string",      "mediaId": "string",      "size": 0    }  ]}
{  "error": "string",  "message": "string"}

Was this article helpful?

Related articles

Spotted something off? Tell us at support@zanfia.com.