CrewLink API

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

  1. GET /v1/public/plans for the plan table.
  2. POST /v1/signup creates 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.
  3. The owner enters their card on Stripe's page. Stripe sends them back to https://go.crewlink.ai/welcome?session_id=<id>.
  4. /welcome calls POST /v1/signup/complete with 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:

json
{
  "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
bash
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:

json
{ "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:

json
{
  "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

CrewLink API · Reference · Changelog