billing

POST /v1/trials

Start a free trial on a plan.

All billing endpoints

All developer docs →

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 \
  -H "Authorization: Bearer zdk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "plan_code": <string> }'

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

docs/20. The endpoint the "Start trial" buttons had nowhere to point at. Three admin trial endpoints existed — list templates, edit a template, staff-extend a trial — and no customer-facing one, so all 47 CTAs on the public pricing page linked to ?intent=register: register first, which is the opposite of what the button says. ⛔ plan_code, never a product line. Eligibility is a property of the plan (owner ruling 2026-07-29): cloud and VPS live in the same line as plans that do carry a trial, so a line-shaped request could not express "never on billable per-instance compute" at all. One trial per organization per product line (owner ruling 2026-10-16, s1014 L58: an account may trial WordPress AND PBN, once each), enforced by a database constraint as well as a check — so a double-click races safely and the loser is refused rather than granted a second trial. Existing and paying customers may trial any line they never had; a plan on a line refuses only that line's trial (refusal.trial.alreadySubscribedLine). ⚖️ 2026-10-21 (ZEAB2): a card-required trial (Developer Hosting — 14 days, or 30 with campaign: zeabur) is refused with refusal.trial.cardRequired until a verified card is on file; the card is never charged.

Request body

NameTypeRequiredWhat it is
plan_codestringYes—
org_idstringNoRequired only when the caller bills for more than one organization. With exactly one it is unambiguous; with several it is asked for rather than guessed, because starting a…
auto_convert_opt_inbooleanNo⛔ Defaults to false — the direction that cannot take money nobody agreed to.
device_idstringNodocs/1000 §1 — the dashboard's random per-browser id, used only by the free-trial abuse guard ("same browser as an account that already had a trial"). Stored only as a keyed hash.
campaignstringNo⚖️ A campaign-only trial (owner 2026-10-21 — the "Move from Zeabur" page's 30-day Developer Hosting trial, zeabur). Honoured only when an active template of that campaign is…

Response

NameTypeRequiredWhat it is
idstringYes—
org_idstringYes—
statusstring<active, expired, converted, canceled, switched>Yes—
product_linestringYesThe 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_sitesintegerYes—
started_atstringYes—
ends_atstringYes—
auto_convert_opt_inbooleanNoWhether 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_remainingintegerNoWhole 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…
upgradeTrialUpgradeOfferNoThe 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_deadlinestringNos1014 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…
planobjectNos1014 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 · 404 · 422