Core concepts

Conventions

Things the Worksible API does the same way for every endpoint. Read this once and you can predict the shape of any new endpoint we ship without opening the reference.

JSON shape

  • camelCase keys in both requests and responses. organizationId, not organization_id.
  • No envelope on resource responses. GET /v1/companies/me returns the org directly, not { data: { ... } }.
  • List envelopes always look the same: { items, page, perPage, total }.
  • Errors always carry an error string.
  • Unknown fields are silently ignoredon writes. We add fields freely; if you sent something we don't understand, you get no warning. Validate on your end if it matters.

Identifiers

IDs are opaque strings. Don't parse them, don't assume any format. Most of the public surface uses prefixed UUIDs (e.g. prj_2025_8f3a), but a few core entities (users, sessions, organizations) use unprefixed strings inherited from the auth layer. Treat all of them as opaque.

Don't sort by id
IDs are not chronological. To order by creation time use the createdAt field, which we always return.

Money

Every monetary amount in the API is an integer in the smallest currency unit (cents for EUR/USD, agorot for ILS, etc.) named with a Cents suffix. Currencies are always sent alongside as ISO 4217.

json
{
  "subtotalCents": 125000,
  "taxAmountCents": 26250,
  "totalCents": 151250,
  "currency": "EUR"
}
// Display: €1,512.50
Never use floats
We don't accept floats on writes. { "amountCents": 12.5 } is a 422 error. Convert in your client.

Timestamps

Every timestamp field is ISO 8601 with a timezone, in UTC by default. Examples: 2026-05-03T08:14:22.000Z. Date-only fields (e.g. issueDate on invoices) are YYYY-MM-DD.

On filters, we accept the full ISO 8601 form or any prefix that maps unambiguously (2026-05 means the entire month).

Pagination

List endpoints accept page (1-indexed, default 1) and perPage (default 25, max 100). The response always includes total so the client can compute the page count up front.

bash
curl "https://api.worksible.com/v1/companies/me/projects?page=2&perPage=50" \
  -H "X-API-Key: $WORKSIBLE_API_KEY"
json
{
  "items": [ /* up to 50 entries */ ],
  "page": 2,
  "perPage": 50,
  "total": 287
}
Cursor pagination is coming
For datasets bigger than ~10k rows, page-based pagination starts to feel slow on the deeper pages. We're rolling out cursor pagination on high-volume endpoints (payments, invoices, timesheets) in 2026 Q3. The cursor field will be additive: existing page/perPage clients keep working unchanged.

Sorting

Endpoints that support sorting accept the sort query parameter. The default is always documented per endpoint and is usually -createdAt (newest first).

bash
# Ascending
GET /v1/companies/me/projects?sort=createdAt

# Descending (default)
GET /v1/companies/me/projects?sort=-createdAt

# Multi-key (status first, then date)
GET /v1/companies/me/projects?sort=status,-createdAt

Filtering

Filters use plain query parameters; we don't use any filter[field]=value notation. Each endpoint documents the fields it filters on. Filter values use these operators:

  • Exact: ?status=active
  • Multiple values (OR): ?status=active,paused
  • Range: ?createdAt[gte]=2026-01-01&createdAt[lt]=2026-04-01
  • Substring search: a dedicated search parameter runs case-insensitive matching on the natural “name” field of the resource.

Field selection

Most endpoints accept fields as a comma-separated list to slim the payload. Useful when you're paginating millions of rows and only need the id + a couple of columns.

bash
GET /v1/companies/me/projects?fields=id,title,status,createdAt

Expanding relations

Foreign keys are exposed as ids by default. Pass expand to inline the related resource. Multiple values comma-separated.

bash
GET /v1/companies/me/invoices/inv_123?expand=recipient,payment
json
{
  "id": "inv_123",
  "totalCents": 151250,
  "recipientOrganizationId": "org_2025_acme",
  "recipient": {
    "id": "org_2025_acme",
    "name": "Acme Corp",
    "vatId": "ESB12345678"
  },
  "paymentId": "pay_889",
  "payment": {
    "id": "pay_889",
    "status": "succeeded",
    "rail": "stripe_card"
  }
}

Request IDs

Every response carries an X-Request-Id header (server-assigned UUID). Include it when you contact support — we can use it to find the exact log line in seconds rather than hours.

Locale

Localised content (notification copy, default email subjects, etc.) follows the Accept-Language header. We support es (default), en, fr, it, pt, de, zh, and ar. Unknown locales fall back to en.