Admin (CrewLink staff)
The admin area is for CrewLink's own staff, not for customers. Super admins see every company, open any of them as its owner for an hour to help (audited, with limits), look at platforms and their grants, manage who else is a super admin, and read the audit log. A company never needs any of this; it's documented so the people who run CrewLink, and the developers after them, know exactly what it can and can't do.
- Who is a super admin
- GET /v1/admin/companies
- Act-as: POST /v1/admin/companies/{id}/act-as
- GET /v1/admin/platforms
- GET /v1/admin/users and POST /v1/admin/users/{id}/superadmin
- GET /v1/admin/actions (the audit log)
- The ops pages
#Who is a super admin
CrewLink runs an internal company, slug crewlink, kind: "platform". It is never billed, has no booking page (GET /v1/public/crewlink is 404), and can't be a CrewConnect partner (404 to connect to it). Its people sign in like anyone else: an emailed code or a passkey (sign-in.md). A person there whose superAdmin flag is on is a super admin; GET /v1/office/me says "superAdmin": true.
Everything under /v1/admin needs a super admin signed in as themselves:
| Token | /v1/admin/* |
|---|---|
| A super admin's session or user token | Yes |
Anyone else at crewlink (staff who aren't super admins) |
403 forbidden |
| Any other company's people, API key or a platform key | 403 forbidden (401 for a bad token) |
| An act-as session, even a super admin's | 403 acting_as (exit first) |
The flag only counts on the crewlink company: set on anyone else's user it does nothing.
#GET /v1/admin/companies
Every company, newest activity first.
| Query | |
|---|---|
q |
Part of the name or slug, any case (% and _ are literal) |
kind |
company, individual, client or platform |
limit |
Default 100, at most 500 |
curl -s "$CREWLINK/v1/admin/companies?q=north" -H "Authorization: Bearer $ADMIN"{
"total": 1,
"companies": [
{
"id": "3f6c2a9e-8b1d-4c7a-9e2f-5d4b1a7c8e90",
"slug": "northline",
"name": "Northline Heating & Air",
"kind": "company",
"plan": "crew",
"planStatus": "trialing",
"demo": true,
"managedBy": null,
"counts": { "users": 3, "techs": 8, "jobs": 2412, "jobs30d": 611 },
"createdAt": "2026-10-10T14:02:11.000Z",
"lastActivityAt": "2026-10-13T15:41:08.000Z"
}
]
}| Field | Notes |
|---|---|
plan, planStatus |
The CrewLink plan and its Stripe status; null until the company has a subscription row |
demo |
A demo account (filled by cli.ts demo-fill) |
managedBy |
Client companies: the provider that set them up |
counts |
Active sign-ins, active people on the schedule, jobs all time and created in the last 30 days |
lastActivityAt |
The latest job created or sign-in started; act-as sessions don't count |
Counts only: no customers, addresses or job details. To see those, act as the company.
#POST /v1/admin/companies/{id}/act-as
Opens a 1-hour session as the company's owner, to see and fix what they see (support, a sales demo).
curl -s -X POST "$CREWLINK/v1/admin/companies/$COMPANY/act-as" -H "Authorization: Bearer $ADMIN" \
-H 'content-type: application/json' -d '{"reason": "ticket 1042: board looks empty"}'{ "token": "9b3e...-....-....-............_secret", "expiresAt": "2026-10-13T16:41:08.000Z", "company": { "id": "3f6c...", "name": "Northline Heating & Air", "slug": "northline" } }Use token as a bearer token on the office API. GET /v1/office/me answers as the owner with userId: null and actingAs: { adminUserId, adminName, expiresAt }. End it early with POST /v1/auth/logout using the act-as token.
What it can do: everything the company's owner can do in the office API, with these exceptions, which answer 403 acting_as:
| Never while acting | |
|---|---|
/v1/admin/* |
Exit first: an act-as session can't act as another company or promote anyone |
POST /v1/office/payouts..., POST /v1/payments/... |
Payouts and payments |
PATCH /v1/office/plan, writes under /v1/office/billing |
The plan, billing and the card on file |
POST /v1/office/pay/periods/{start}/close, /paid |
Closing or paying a pay week |
/v1/office/api-key/..., /v1/office/integration-key/..., /v1/office/clients/{id}/api-key, POST /v1/office/users/{id}/token |
Any key or sign-in token |
Writes under /v1/office/platforms |
Platform grants |
/v1/auth/passkeys... |
Passkeys (they belong to a person) |
Nothing filed while acting is credited to a person (it acts like the company API key: createdByUserId is null), and Idempotency-Keys are kept per admin and company.
Audit. Opening it, every request made with it (method, path, status) and ending it each write a row to the audit log with the admin's user id and the company. The session also stops working the moment the admin is no longer an active super admin.
| Errors | |
|---|---|
404 not_found |
No company with that id |
422 invalid_request |
That's the crewlink company itself |
#GET /v1/admin/platforms
Every platform (platforms.md) with whether its key is disabled, its calls in the last 7 days and every grant, live and revoked, newest first:
{
"platforms": [
{
"id": "6d1f...", "slug": "acme", "name": "Acme", "createdAt": "2026-10-12T09:00:00.000Z", "disabledAt": null, "calls7d": 18234,
"grants": [
{ "company": { "id": "3f6c...", "name": "Northline Heating & Air", "slug": "northline" }, "scopes": ["read", "webhooks", "write"], "grantedAt": "2026-10-12T09:01:00.000Z", "revokedAt": null, "grantedBy": null }
]
}
]
}grantedBy is the owner's user id, or null when CrewLink granted it (cli.ts platform-grant). Platforms are still created and granted on the server (cli.ts platform-create, platform-grant); this is read-only.
#GET /v1/admin/users and POST /v1/admin/users/{id}/superadmin
GET /v1/admin/users lists CrewLink's own people: { id, name, email, role, active, superAdmin, inviteSentAt }.
To add a super admin: sign in to CrewLink, add the person under Users (POST /v1/office/users, which emails them an invite), then:
curl -s -X POST "$CREWLINK/v1/admin/users/$USER/superadmin" -H "Authorization: Bearer $ADMIN" \
-H 'content-type: application/json' -d '{"superAdmin": true}'{"superAdmin": false} takes it away; their act-as sessions end at once.
| Errors | |
|---|---|
404 not_found |
Not one of CrewLink's own people (a customer's user can never be a super admin) |
409 last_super_admin |
Demoting the last active super admin. Promote someone else first |
422 inactive |
Promoting someone whose sign-in is off |
Both are audited (superadmin_grant, superadmin_revoke).
#GET /v1/admin/actions
The audit log, newest first. Filter with companyId or adminUserId; page back with before (an id); limit default 100, at most 500.
{
"actions": [
{ "id": 812, "admin": { "id": "a1...", "name": "Sam Admin", "email": "sam.admin@example.com" }, "company": { "id": "3f6c...", "name": "Northline Heating & Air", "slug": "northline" }, "action": "act_as_request", "method": "POST", "path": "/v1/office/jobs/4411/move", "status": 200, "sessionId": "9b3e...", "detail": {}, "at": "2026-10-13T15:52:40.000Z" },
{ "id": 811, "admin": { "id": "a1...", "name": "Sam Admin", "email": "sam.admin@example.com" }, "company": { "id": "3f6c...", "name": "Northline Heating & Air", "slug": "northline" }, "action": "act_as_start", "method": "POST", "path": "/v1/admin/companies/3f6c.../act-as", "status": 200, "sessionId": "9b3e...", "detail": { "reason": "ticket 1042" }, "at": "2026-10-13T15:41:08.000Z" }
]
}action |
|
|---|---|
act_as_start |
An act-as session opened (detail.reason if one was given) |
act_as_request |
One request made with it, written once the response has gone out |
act_as_end |
Ended with POST /v1/auth/logout (a session that simply expires has no end row) |
superadmin_grant, superadmin_revoke |
Someone was made, or stopped being, a super admin (detail.userId) |
Rows are never deleted.
#The ops pages
GET /v1/ops/travel and GET /v1/ops/brain, owner only for a company, are also open to super admins: they're platform-wide numbers (road router, drive-time cache, traffic lookups, rush-hour profiles, brain training runs). The web app's Admin page shows them as tiles.