W Work Docs
Open Work →

API

The web app, the CLI and any integration use the same versioned REST API.

Base URL: https://work.betterthangood.xyz/api/v1

Authentication

Create a personal access token at Settings → API tokens, owner-only and send it as a bearer token:

curl https://work.betterthangood.xyz/api/v1/contacts \
  -H "Authorization: Bearer work_pat_…"

The header is the only accepted method. Tokens are not read from query parameters or cookies.

A token acts as the user who created it, on the account it was created in, with that user's access.

Creating a token

FieldNotes
NameA label, for example “Deploy script”
Scopesread, write or both
ExpiresDefaults to 30 days out

The token is displayed once, at creation. Work stores a hash of it and cannot display it again. Replace a lost token by revoking it and creating another.

Scopes

ScopeAllows
readEverything the issuing user can see
writeEverything the issuing user can change. Includes read

Revoking

Tokens are revoked rather than deleted, so audit log entries keep a valid reference. A revoked token stops working immediately.

Resources

ResourceEndpoints
ContactsGET/POST /contacts · GET/PATCH/DELETE /contacts/{id}
CompaniesGET/POST /companies · GET/PATCH/DELETE /companies/{id}
DealsGET/POST /deals · GET/PATCH/DELETE /deals/{id}
ProjectsGET/POST /projects · GET/PATCH/DELETE /projects/{id}
TasksGET/POST /tasks · GET/PATCH/DELETE /tasks/{id}
InvoicesGET/POST /invoices · GET /invoices/{id}
Invoice actionsPOST /invoices/{id}/send · POST /invoices/{id}/payments
PaymentsGET /payments · GET /payments/{id}

Invoices have no update or delete endpoint. Sent invoices are immutable, and drafts are edited in the app.

Payments are created against an invoice at POST /invoices/{id}/payments.

Task access follows roles: owners can list and assign any task; members see and change only their own, and a task created by a member is assigned to them.

Responses

Responses use one envelope. Lists return an array in the same data key.

{ "data": { "id": 42, "first_name": "Ada", "created_at": "2026-08-27T09:15:00Z" } }

Every record carries id, created_at and updated_at.

Money in responses is an integer number of cents, in value_cents and amount_cents fields, with a currency field alongside.

Creating an invoice

POST /api/v1/invoices
{ "invoice": {
    "company_id": 7, "contact_id": 12,
    "issue_on": "2026-08-27", "due_on": "2026-09-26",
    "lines": [
      { "description": "Design retainer", "quantity": "1", "unit_price": "1200.50"
      }
    ] } }

lines is required. An invalid line rolls back the whole request.

Line unit_price is submitted in dollars ("1200.50"). Amounts in responses are in cents.

  • POST /invoices/{id}/send emails the client and freezes the figures. It returns 422 if the invoice is already sent, has no line items or has no recipient.
  • POST /invoices/{id}/payments takes amount in dollars, paid_on and reference. It is refused on drafts.

Both return the full invoice.

Pagination

GET /api/v1/contacts?page=2&per_page=50

Default 25 per page, maximum 100. Values outside the range fall back to the default. There is no next-page marker; a page shorter than per_page is the last one.

Errors

{ "error": { "code": "forbidden", "message": "This token does not have the write scope." } }
StatusMeaning
401Token missing, unknown, revoked or expired, or its user has left the account
402The account's subscription is not active
403The token lacks the required scope
404No such record in this account, or no such route
422The save failed. details names the fields
429Rate limit exceeded

A record in another account returns 404 rather than 403, so that IDs cannot be tested for existence.

Rate limits

1,000 requests per hour per token, across all endpoints. Exceeding it returns 429.

Audit

API writes are recorded in the audit log with the token's user and the request IP address.

Versioning

  • New fields are additive and may appear without notice. Clients should ignore unrecognized keys.
  • Breaking changes ship as a new version at /api/v2, alongside v1.
  • v1 would then be deprecated on a published timeline. Response shapes are not changed in place.

Not currently available

Webhooks and OAuth apps are planned. Integrations poll the API and authenticate with personal access tokens.

The specification

The OpenAPI specification is generated from the server's routes and contract-tested against them. Use it to generate a client.