Skip to content

Changelog

Dated to match API versions — additive changes that don’t need a version bump are noted here too, under the version they shipped alongside. Changes to the OAuth endpoints, which aren’t versioned, get their own dated entry.

2026-09-23 — Standard OAuth error responses

Section titled “2026-09-23 — Standard OAuth error responses”

POST /oauth/token, /oauth/revoke, /oauth/introspect, /oauth/device/authorize and /oauth/register now answer errors the way the OAuth specifications define (RFC 6749 §5.2, RFC 7009, RFC 7662, RFC 8628 §3.5, RFC 7591 §3.2.2), so standard OAuth libraries can read them. The OAuth code moves from message to error, and any detail to error_description:

// before
{ "statusCode": 400, "message": "invalid_grant: code already used", "error": "Bad Request" }
// now
{ "error": "invalid_grant", "error_description": "code already used" }
  • Status codes: 400 for most errors; 401 for invalid_client, with WWW-Authenticate: Basic realm="OneBooks" when you authenticated with HTTP Basic; 429 with Retry-After and "error": "temporarily_unavailable" when a rate limit refuses the request; 5xx with "error": "server_error". Every error response carries Cache-Control: no-store.
  • Device authorization: an unknown, suspended or deactivated client_id now gets 401 invalid_client (it was 400), and a scope your app didn’t register gets invalid_scope, with the scopes named in error_description. The request now takes the RFC 8707 resource parameter — invalid_target unless it’s on this API’s origin, as on /oauth/token — and, like the other standard endpoints, ignores parameters it doesn’t recognize instead of answering 400.
  • Client authentication on device authorization: POST /oauth/device/authorize now authenticates the client exactly as /oauth/token does (RFC 8628 §3.1). HTTP Basic works — the body then needs no client_id (it used to get 400). A CONFIDENTIAL client must send its secret, and a secret you send must be correct whatever the client type: a missing or wrong one is 401 invalid_client, where it used to be accepted unchecked until the token poll. Failures count toward this endpoint’s own failed-authentication lockout. A PUBLIC client sending its client_id alone is unaffected. See device authorization.
  • API reference: the OpenAPI document listed the Authorization header as required on /oauth/token and /oauth/revoke. It’s optional there, as on /oauth/device/authorize: HTTP Basic is one way to send client credentials, client_id + client_secret in the body the other, and a PUBLIC client sends no secret. A client generated from the document can drop the header. /oauth/introspect still requires it — introspection reads client credentials from the header only.
  • resource on the authorization request: GET /oauth/authorize — the call the consent page makes with your authorization request — now checks an RFC 8707 resource by the token endpoint’s rule instead of ignoring it: anything but this API’s origin is invalid_target, which the consent page shows the user; nothing is redirected to your app.
  • Unrecognized token_type_hint: /oauth/revoke and /oauth/introspect now ignore a hint other than access_token or refresh_token, as RFC 7009 requires. It used to get 400 — and the token wasn’t revoked.
  • Unchanged: every other endpoint — including OneBooks’ own /oauth/* routes behind the consent and device-approval pages — keeps the { statusCode, message, error, code } shape described in Errors.
  • Deprecated message: for backward compatibility with older OneBooks clients, the error body still carries message too, with the value it used to have ("invalid_grant: code already used" above). It will be removed in a later release and must not be relied on.

This isn’t a new API version and OneBooks-Version doesn’t affect it: it applies to every client. What to update: if your code reads message from a failed token, revocation, introspection, device-authorization or registration request, or matches its text, read error instead. Where the old message started with an OAuth code (invalid_grant: expired), that code is now error and the rest is error_description; an error whose message was only prose now carries the matching code — invalid_client for a 401, otherwise invalid_request — with the prose as its description. Log error_description, but don’t branch on it. A client built on a standard OAuth library needs no change. The Node SDK exposes the code as err.oauthError and reads both shapes. See OAuth endpoint errors for every code.

The partner API becomes an app platform: apps can run inside OneBooks, not just alongside it.

  • Embedded apps — app pages and 15 UI extension targets rendered in sandboxed iframes, secured by short-lived session tokens and RFC 8693 token exchange. New: App Bridge protocol v1.
  • App data — typed, app-owned fields on invoices, customers and 10 other resource types, optionally merchant-editable. See App data.
  • Hosted functions — run your own JavaScript on OneBooks events in a sandboxed, declared-egress V8 isolate. See Hosted functions.
  • Marketplace — reviewed, localized listings; ratings and reviews; managed one-click install. See Publishing your app.
  • Event catalog expansion — 45 events across every domain module, among them purchase.updated, quote.deleted, purchase_return.refunded and the supplier.redact privacy notice, plus a durable Events API catch-up feed with 30-day retention. Webhook deliveries now carry eventId and apiVersion, support signing-secret rotation with dual v1= signatures during the overlap window, and can be redelivered from the console. See the Event catalog and Webhooks.
  • New scopes: app-data:read, app-data:write, events:read. See Scopes.
  • Per-app rate limits — OAuth-authenticated API calls are now limited per app rather than per IP: 300 requests a minute per app per business, and 10,000 a minute per app across all businesses, with RateLimit-* response headers describing the per-business budget. On POST /oauth/token, a confidential client now gets its own budget of 600 requests a minute across all its IPs; token, introspection and revocation requests are counted per IP and client (600 a minute with a secret, 300 without) under a 1,200-a-minute per-IP ceiling, and failed client authentications lock out that IP-and-client pair — not the whole IP — for a minute. See Rate limits.
  • Stricter, interoperable client authentication — any client secret a request presents to /oauth/* must be correct, whatever the client type; and a public client can now revoke its own tokens by sending just its client_id (RFC 7009), where it previously got 401. See Revocation.
  • Date-based API versioning — OneBooks-Version, pinnable (and unpinnable) per app in the console, 12 months of support per version; hosted functions’ ctx.api follows your app’s pin too. Current version: 2026-09-22. See API versioning.
  • New SDKs: @onebooks/node, @onebooks/app-bridge, @onebooks/functions, the onebooks CLI and the embedded-node starter template — in preview: the packages are being published to npm; until then, email developers@getonebooks.com for early access.