billing
POST /v1/trials/winback
Take the one-time 7-day extension — the ended trial runs again for 7 days.
Authentication
Send an API key as a bearer token. The endpoint checks billing.view; a key without it is refused with 403, not 404.
Where your organisation id goes
This endpoint takes org_id as a field in the JSON body.
Your organisation id is on the API keys screen in your dashboard, beside the key itself. It is the same id in every call you make.
Try it
Replace anything in angle brackets with your own values, and the key placeholder with a key from your dashboard.
curl -X POST https://api.zinndigital.com/v1/trials/winback \
-H "Authorization: Bearer zdk_live_…" \
-H "Content-Type: application/json" \
-d '{ }'Signed in? The API console in your dashboard fills in your real organisation id and your own key, and runs the request against the live API so you can see the actual response. Open this endpoint in the API console
Details
s1014 L55. The SAME trial row runs again until now + 7 days: same product line, same site limit, same plan; nothing is upgraded and nothing is charged. Sites the trial's end took offline come back, unless another enforcement (malware, abuse) holds them. Only a signed-in person may take it — not an API key and not a staff impersonation session (refusal.trial.winback.personOnly). Refusals: refusal.trial.winback.alreadyUsed (this person or this account already took it), refusal.trial.winback.notOffered. Audit-logged as trial.winback_extended.
Request body
| Name | Type | Required | What it is |
|---|---|---|---|
org_id | string | No | Required when the caller bills for several organizations. |
Response
| Name | Type | Required | What it is |
|---|---|---|---|
id | string | Yes | — |
org_id | string | Yes | — |
status | string<active, expired, converted, canceled, switched> | Yes | — |
product_line | string | Yes | The line's CODE, not its display name — the dashboard renders the label from its own catalogue, so prose here would be a second, untranslated source of that word on the screen. |
max_sites | integer | Yes | — |
started_at | string | Yes | — |
ends_at | string | Yes | — |
auto_convert_opt_in | boolean | No | Whether the customer agreed at the start that this may become a paid subscription when the period ends. Captured per trial rather than read from the template at expiry, so… |
days_remaining | integer | No | Whole days until ends_at, rounded UP and floored at zero. Computed on the server on purpose: a client doing (ends_at - Date.now()) measures the device clock, so a laptop a… |
upgrade | TrialUpgradeOffer | No | The plan this trial invites the customer to buy, priced in their own currency (X-Zinn-Currency, falling back to the org default). ⛔ null when the template names no target or… |
switch_deadline | string | No | s1014 L58 (owner 2026-10-16). Until when this trial may still be switched to another product (POST /v1/trials/{trial_id}/switch): its first three days. null when it cannot be… |
plan | object | No | s1014 L70. The plan this trial was started for (the one the customer clicked) and the application it installs, so the create-site form opens on that application — a "Moodle Pro"… |
Errors this endpoint can return
401 · 403 · 422