Skip to main content
Foundations

Authentication

All /public/v1/* requests require a Bearer access token minted for a marketplace app install. The token carries the tenant scope (org + entity) and the granted scopes.

How it works

The HelloBooks Public API lives at /public/v1/* and is gated by a marketplace-scoped Bearer token.

When a HelloBooks user installs your marketplace app into one of their entities, the OAuth flow returns an access token whose JWT aud claim is marketplace_app. That token is bound to a single AppInstall record, which carries:

  • installId — the install's database ID
  • clientId — your marketplace app's client identifier
  • appSlug — your app slug
  • orgRefId + entityRefId — the tenant the user installed your app into
  • scopes — the OAuth scopes the user granted

Send the token on every request:

Authorization: Bearer <access_token>
Host placeholder. Examples below use api.hellobooks.com as a stand-in. Replace it with your environment's host — production, staging, and self-hosted deployments each have their own. Confirm the correct host with your HelloBooks contact before going live.

Verify your install

Call GET /public/v1/me with the token to confirm the tenant scope and the granted scopes.

curl -H "Authorization: Bearer $HB_TOKEN" \
  https://api.hellobooks.com/public/v1/me
{
  "install_id": "65f0a1...",
  "client_id": "cli_abc123",
  "app_slug": "your-app-slug",
  "org_ref_id": "65...",
  "entity_ref_id": "66...",
  "scopes": ["invoices:read", "bills:read"]
}

The values in org_ref_id and entity_ref_id MUST match the :orgId/:entityId you put in the URL of any tenant-scoped route (see Tenant Isolation).

Token rotation

Marketplace access tokens carry an explicit AccessTokenExpiresAt. When the token expires, calls return:

{ "error": "invalid_token", "message": "Access token expired." }

Use your refresh token (issued at the end of the install flow) to mint a new access token. Disabling your AppApiKey from the marketplace dashboard immediately kills outstanding tokens — every request after that returns invalid_client.

Marketplace tokens are NOT user tokens

HelloBooks runs two separate authentication surfaces:

SurfaceURL prefixToken type
Internal app (used by HelloBooks frontend)/<resource>/... (e.g. /invoices/read/...)User session JWT
Public API (marketplace apps)/public/v1/*Marketplace access token

The user-token middleware explicitly rejects marketplace tokens with Marketplace app tokens cannot be used on user routes. Conversely, a user JWT will not authenticate at /public/v1/*. Pick the right surface for the right caller.

Frequently asked questions

My token returns invalid_token. What changed?

Three common causes: (1) the user uninstalled your app — the AppInstall row is no longer Status: active; (2) the AppApiKey behind your install was disabled by the marketplace admin; (3) the access token expired. Refresh and retry. If the issue persists, re-run the install OAuth flow.

Can I use the same token across multiple entities for the same user?

No. Tokens are scoped per AppInstall, and each install is for one (org, entity) pair. If your app is installed across three entities, you hold three separate access tokens. The /me endpoint tells you which scope a token belongs to.

What happens if I send a token to /invoices/read instead of /public/v1/orgs/:orgId/entities/:entityId/invoices?

The internal app middleware rejects marketplace tokens at the audience check and returns 401 with the message "Marketplace app tokens cannot be used on user routes." Always use the /public/v1/* prefix for marketplace traffic.

    HelloBooks Public API — Authentication