Sign-up
Self-serve sign-up for CrewLink: the published plans and rates, creating a company with its owner, the card on file through Stripe Checkout, and the welcome sign-in. These endpoints are public (no key). The web app's /signup and /welcome pages use them; you only need them to build your own sign-up flow.
The plans and the billing rules are in office.md, Your CrewLink plan and, in full, docs/dev/pricing.md.
#The flow
GET /v1/public/plansfor the plan table.POST /v1/signupcreates the company, its owner and the chosen plan, and returns a Stripe Checkout link (a subscription with a 30-day free trial; the card is collected up front). Enterprise is not self-serve: book a call.- The owner enters their card on Stripe's page. Stripe sends them back to
https://go.crewlink.ai/welcome?session_id=<id>. /welcomecallsPOST /v1/signup/completewith that id. It confirms the checkout with Stripe and returns a 30-day session for the owner, once. CrewLink also emails the owner, so next time they sign in with their email (sign-in.md).
A Connect seat (a 1099 contractor who only takes CrewConnect work) is free: no card, and step 2 returns a welcomeUrl straight to step 4.
Scheduling is never blocked by any of this: not during the trial, not over the included jobs, not after a failed payment.
#GET /v1/public/plans
Response 200:
{
"plans": [
{ "code": "connect", "name": "Connect", "monthlyCents": 0, "annualCents": 0, "includedJobs": 0, "overageCents": 0, "perJobCents": 0, "capAt": null, "breakeven": null, "next": null, "selfServe": true, "for": "individual", "maxUsers": 2, "multiCrewBoard": false, "who": "1099 contractors who only take CrewConnect work: free. Booking your own jobs moves you to Independent." },
{ "code": "independent", "name": "Independent", "monthlyCents": 3900, "annualCents": 39000, "includedJobs": 75, "overageCents": 75, "perJobCents": 52, "capAt": 222, "breakeven": 222, "next": "crew", "selfServe": true, "for": "individual", "maxUsers": 2, "multiCrewBoard": false, "who": "1099 contractor, owner + 1 helper login; no multi-crew dispatch board" },
{ "code": "crew", "name": "Crew", "monthlyCents": 14900, "annualCents": 149000, "includedJobs": 300, "overageCents": 65, "perJobCents": 49.67, "capAt": 608, "breakeven": 608, "next": "shop", "selfServe": true, "for": "any", "maxUsers": null, "multiCrewBoard": true, "who": "Companies, 2–4 trucks (service trades); crew trades up to ~8" },
{ "code": "shop", "…": "…" },
{ "code": "fleet", "…": "…" },
{ "code": "enterprise", "name": "Enterprise", "monthlyCents": 199900, "annualCents": 1999000, "includedJobs": 6000, "overageCents": 38, "selfServe": false, "…": "…" }
],
"rates": { "card": { "rateBp": 290, "fixedCents": 30 }, "ach": { "rateBp": 100, "capCents": 1500 }, "platformBp": 100, "payouts": { "standardBp": 0, "instantBp": 100 } },
"trialDays": 30,
"contactSalesAbove": 15000,
"enterpriseCallUrl": "https://crewlink.ai/#walkthrough"
}| Field | Notes |
|---|---|
annualCents |
A year's price: 10 months for 12 ("2 months free"), with 12 months of jobs pooled |
perJobCents |
The monthly price over the included jobs, in cents (Crew: 49.67, about $0.50 a job) |
capAt |
Jobs a month where the best-price cap starts: the month never costs more than next's monthly price |
breakeven |
Jobs a month where next becomes the cheaper plan |
selfServe |
false for Enterprise: show "Book a call" with enterpriseCallUrl |
for |
individual: only for 1099 contractors (kind: individual) |
rates |
The all-in payment rates: card 2.9% + 30¢, ACH 1% capped at $15, with CrewLink's 1% inside them; CrewConnect payouts free, or 1% for an instant payout |
#POST /v1/signup
Body:
| Name | Type | Required | Notes |
|---|---|---|---|
company |
string | yes | 2 to 120 characters. The booking-page slug is made from it (Bright Air → bright-air, then bright-air-2 if taken) |
name |
string | yes | The owner's name |
email |
string | yes | The owner signs in with it (stored lower case) |
timezone |
string | no | IANA zone, default America/Chicago |
country |
string | no | ISO 3166-1 alpha-2 of the head office, default US. Which phone numbers get texts, how numbers are read, address search and the usual distance units (changeable in Settings). 422 for an unknown one |
trade |
string | no | What you do, e.g. hvac, up to 60 characters |
kind |
string | no | company (default) or individual (a 1099 contractor) |
plan |
string | yes | A plan code |
interval |
string | no | month (default) or year |
website |
string | no | Leave it out. A form that fills it in (a bot) gets an ordinary-looking 201 and nothing is created |
curl -s -X POST "$CREWLINK/v1/signup" -H 'Content-Type: application/json' \
-d '{"company":"Bright Air","name":"Bea Bright","email":"bea@brightair.example","trade":"hvac","plan":"crew","interval":"month"}'Response 201:
{ "companyId": "0b6f…", "slug": "bright-air", "checkoutUrl": "https://checkout.stripe.com/c/pay/cs_live_…", "welcomeUrl": null }Send the browser to checkoutUrl. For a Connect seat checkoutUrl is null and welcomeUrl is the /welcome link.
Errors: 422 contact_sales with url (Enterprise), 422 invalid_request (Independent or Connect for a company, a yearly Connect seat, an unknown time zone, a bad field), 429 (5 a minute per IP), 502 billing_unavailable (Stripe didn't answer; nothing was created).
Until the card is confirmed the account's subscription.status is incomplete. If the owner leaves Checkout without finishing, the company exists and they can still sign in with their email; the office shows "Add a card to start your free trial".
#POST /v1/signup/complete
Body: { "sessionId": "<the session_id from the /welcome URL>" }.
Response 200: the same session as POST /v1/auth/verify, plus the plan and when the trial ends:
{
"token": "6c1e…",
"expiresAt": "2026-11-19T17:00:00.000Z",
"user": { "id": "…", "name": "Bea Bright", "role": "owner", "personId": null },
"company": { "id": "0b6f…", "name": "Bright Air", "slug": "bright-air" },
"plan": "crew",
"trialEndsAt": "2026-11-19T06:00:00.000Z"
}It works once, within 24 hours of sign-up. Errors: 409 checkout_incomplete (Stripe hasn't confirmed the card yet: try again in a couple of seconds), 409 already_used (sign in with your email instead), 404 not_found (unknown or not matching), 429 (10 a minute per IP).
#Stripe webhook events
CrewLink's Stripe webhook (POST /v1/payments/stripe/webhook, signed with STRIPE_WEBHOOK_SECRET) handles these subscription events besides customer payments. They're Stripe-to-CrewLink only; your integration doesn't call them, but they explain what changes on the account:
| Event | What CrewLink does |
|---|---|
checkout.session.completed |
Puts the plan, interval, Stripe customer and subscription on the company (the same as /welcome) |
customer.subscription.created, customer.subscription.updated |
Keeps status (trialing, active, past_due, canceled, incomplete) and the trial end. A price changed in the Customer Portal moves the plan with the same rules as PATCH /v1/office/plan |
customer.subscription.deleted |
status: canceled |
invoice.payment_failed |
status: past_due: a banner in the office, nothing else. Scheduling carries on |
invoice.paid |
Clears past_due |