Paywall
The paywall is the set of Easycart products linked to a course. These routes stay at /players/{id}/products. The API does not create an Easycart product. It links one that already exists.
Overview
Connect, update, and disconnect are live. Visitors see the change immediately, including on a published course. These routes are not draft edits. They send no draft_version and return no preview_url and no draft fields. Publish and unpublish do not change the paywall. A pending draft for other sections stays in place. See Draft, preview, and publish.
product_id and every variant_ids entry are Easycart UUIDs. {productId} in the path is that same product UUID.
List Products is paginated. include=products on Get Course returns every link in one array, not a page. Zero links is []. Leave products out of include and the key stays null.
Connecting a product on a published course makes a free course paid. Disconnecting the last product makes it free. Narrowing variant_ids cuts off buyers of the variants you removed. The course link and the matching Easycart link are saved together. If Easycart refuses the link, nothing is saved.
Every paywall route reads or writes Easycart. When Easycart is unreachable or failing, the call returns 503 SERVICE_UNAVAILABLE; when it answers with something the API cannot use, 502 UPSTREAM_ERROR. Nothing is saved in either case. A workspace that is not connected to an Easycart store returns 422 VALIDATION_ERROR.
Connecting a product that is already linked returns 409 DUPLICATE_RESOURCE. Change its variants with Update Product Link.
Disconnect Product removes the link. The Easycart product stays. The response is 204 with no body.
A course id that is missing or not a UUID returns 404. On update and disconnect, a product that Easycart resolves and that is not linked to the course returns 404. See Identifiers.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /players/{id}/products | List linked products |
| POST | /players/{id}/products | Connect a product |
| PATCH | /players/{playerId}/products/{productId} | Update a product link |
| DELETE | /players/{playerId}/products/{productId} | Disconnect a product |
Product Link Object
Every field is present.
| Field | Type | Description |
|---|---|---|
product_id | string|null | Easycart product UUID. null when missing is true. |
product_name | string|null | Easycart product name. null when missing is true. |
variant_ids | array|null | Easycart variant UUIDs. null means every current and future variant. [] means the product still resolves and every stored variant is gone. |
missing | boolean | true when Easycart no longer resolves the stored product. Otherwise false. |
Variants
variant_ids: null means every current and future variant. Omitted or null on connect links every variant. A non-empty list restricts the link to those variants. [] on connect or update is 422. On a read, [] means the product still resolves and every stored variant is gone. Sending [] back does not open the link to every variant. Send null for every variant.
A list holds at most 100 variant UUIDs. A repeated UUID is kept once. The variants come back in the order you sent.
A variant UUID that is unknown, or that belongs to a different product, is 422.
Missing products
A stored product Easycart no longer resolves is still listed, because it still gates the course:
{
"product_id": null,
"product_name": null,
"variant_ids": null,
"missing": true
}
Other rows have missing: false. That row has no product UUID, so it cannot be updated or disconnected. A product UUID that Easycart does not resolve is 422. Remove that link in the editor.