API

Search the developer hub

Jump to a page or an endpoint.

API reference

The public OPS25 HTTP API. JSON in, JSON out, scoped to one organisation.

The OPS25 API is organised around REST. Resource-oriented URLs, JSON request bodies, JSON responses, and standard HTTP verbs and status codes.

There are two parts to it:

Part Paths Authenticated by Use it for
The API /api/v1/… A secret API key Your own systems: reading orders and jobs, creating orders
Public booking /api/public/… Nothing, or an allowlisted browser origin Booking pages and quote tools that run in a browser

Authentication

API keys

Everything under /api/v1 needs an API key. An owner or admin of the organisation creates keys in the office under Settings → API Keys. Each key belongs to one organisation, so there is no organisation in the URL: the key decides whose data you see.

Send the key as a bearer token in the Authorization header:

curl https://api.ops25.com/api/v1/organisation \
  -H "Authorization: Bearer ops25_sk_…"

That call returns the organisation the key belongs to, which makes it the quickest check that a key works.

Keys have one of two access levels, chosen when the key is created:

Access Can
Read only Every GET under /api/v1
Read and write Everything a read-only key can, and create orders

A few rules keep keys safe:

  • The key is shown once, when it is created. OPS25 stores only a hash of it, so nobody can show it to you again. If you lose it, revoke it and create another.
  • Keep keys on your server. Never put one in a browser, a mobile app or a public repository. Anyone holding a key can act as the organisation. Every key starts with ops25_sk_, so secret scanners can spot a leaked one.
  • Revoking is immediate. A revoked key is refused on its next request with 401.
  • Use one key per integration, named after it. Settings shows when each key was last used, so you can tell which keys are still in use before revoking any.
  • Keys only open /api/v1. They never reach the office's own routes, and the office session never reaches /api/v1.

A missing, malformed or revoked key gets 401:

{ "error": "This API key is not valid. It may have been revoked." }

A read-only key that tries to write gets 403. When the organisation's subscription has lapsed, reads keep working and writes get 402.

Public booking routes

The /api/public/{organisation} routes need no key. They name the organisation in the URL by its booking slug, the same one that appears in its booking page address.

  • Reads (GET) need no credentials and work from a server or a browser.
  • Writes need an allowlisted Origin. A request with no Origin, such as a server, a script or a terminal, is refused with 403 and {"error":"Untrusted request origin"}. Email developers@ops25.com with the origin you will call from, or use an API key and POST /api/v1/orders from your server instead.

Requests

The base URL is:

https://api.ops25.com

Send Content-Type: application/json on every write; anything else is 415. Bodies are capped at 16 KB. Lists come back as { "object": "list", "data": [...], "hasMore": false }.

Errors

Errors are always the same shape:

{ "error": "Enter a valid delivery date." }

The message is written for a person and is safe to show to one. Match on the status, not the text.

Status Meaning
400 Malformed JSON.
401 The API key is missing, malformed or revoked.
402 The organisation's subscription is inactive, so writes are closed.
403 Untrusted origin, a read-only API key used to write, or a payment key that does not match the record.
404 No organisation, or no record, with that identifier.
415 The request was not application/json.
422 Validation failed. The message names the fields, semicolon-separated.
429 Rate limited. Retry after the Retry-After header.
500 Something broke on our side. Safe to retry.

Rate limits

Each API key may make 600 requests per minute. Public booking endpoints allow 240 requests per minute, counted per endpoint across all callers, so treat that as a shared budget. A limited request comes back 429 with Retry-After: 60. Cache the catalogue in your own app rather than polling it.

Idempotent requests

Both ways of creating an order accept an Idempotency-Key header. Replaying the same key against the same organisation returns the original order rather than creating a second one. Use a fresh UUID per booking attempt, and reuse it on retries.