LedgerBee Developer
  • Getting started
  • Conventions
  • Products
  • Configuration
  • API Reference
IntroductionGetting StartedAuthenticationConnected AppsAPI KeysLicenses
Getting started

Connected Apps

A connected app is an OAuth 2.1 client LedgerBee registers for your product, so it can act on a company's data with that company's consent. Register once, then run the authorization-code flow for every company that connects.

Registration

Registration is handled by LedgerBee, not self-service. Email development@ledgerbee.com with:

  • your app's display name
  • one or more https redirect URIs
  • the scopes your integration needs
  • a logo URL (optional)

You get back a client_id (lb_app_client_…) and a client_secret (lb_app_secret_…). The secret is shown once and cannot be retrieved, so store it before you close the message.

Email the same address to change any of this later, or to rotate the secret. A rotation keeps the previous secret working for 24 hours, so you can roll credentials without downtime.

Authorize

Send the company's user to:

Code
GET https://api.ledgerbee.com/auth/oauth/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https://yourapp.com/callback &state=RANDOM_STATE &code_challenge=CODE_CHALLENGE &code_challenge_method=S256 &scope=customers-read%20customers-write

Wrapped for readability; every parameter belongs on one query string, and the space between scope names must be encoded as %20.

ParameterRequiredNotes
response_typeYesMust be code.
client_idYesYour lb_app_client_… id.
redirect_uriYesMust exactly match one of your registered https URIs.
stateRecommendedEchoed back on the redirect. Generate an unguessable value per request and reject a callback whose state doesn't match.
code_challengeYesPKCE, RFC 7636.
code_challenge_methodYesMust be S256; plain is refused.
scopeNoSpace-separated, and a subset of your registered scopes. Omit to request all of them.

A signed-out user is sent to login and returned here afterward.

Consent

The user picks the company to connect if they belong to more than one, then approves or denies the requested scopes as a fixed list. Approval grants exactly what /authorize requested; there is no partial grant. A company holds one live grant per app, so reconnecting refreshes it rather than adding a second.

Approval redirects to your redirect_uri with code and state; denial redirects with error=access_denied.

Token exchange

TerminalCode
curl -X POST https://api.ledgerbee.com/api/v1/auth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=AUTH_CODE" \ -d "code_verifier=CODE_VERIFIER" \ -d "redirect_uri=https://yourapp.com/callback" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=$LEDGERBEE_CLIENT_SECRET"
Code
{ "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "lb_app_refresh_...", "scope": "customers-read customers-write" }
  • access_token is a JWT valid for 1 hour.
  • refresh_token is valid for 30 days, single-use, and rotates on every exchange.
  • scope reports what was actually granted. Read it back rather than assuming your request was granted verbatim.

Authenticate with client_secret_post as above, or client_secret_basic, an Authorization: Basic header carrying client_id:client_secret. The examples read the secret from an environment variable; curl still expands that into its argument list, so on a shared machine use your own HTTP client instead.

This endpoint and /oauth/revoke take a form-urlencoded body (RFC 6749 / RFC 7009), so they are documented here rather than in the API Reference, which covers the JSON REST endpoints.

Refresh

TerminalCode
curl -X POST https://api.ledgerbee.com/api/v1/auth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token" \ -d "refresh_token=YOUR_REFRESH_TOKEN" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=$LEDGERBEE_CLIENT_SECRET"

The 30 days are counted per token: each replacement starts its own window, so refreshing regularly keeps you connected indefinitely while a leaked token still dies 30 days after issue. A connection ends when the tenant revokes it or you go 30 days without refreshing.

Serialize refresh calls per grant across your workers: presenting an already-used refresh token is treated as a compromised token family and revokes the whole grant.

Using the access token

TerminalCode
curl https://api.ledgerbee.com/api/v1/customers \ -H "Authorization: Bearer ACCESS_TOKEN"

See the API Reference for the available endpoints.

Scopes

The scope you request must be a subset of the scopes registered for your app. Three are never grantable to a connected app: checkout, portal-sso-mint, and portal-provision, which are API-key-only provisioning credentials. Legacy vendors-read / vendors-write are accepted at /authorize and normalized to suppliers-read / suppliers-write.

Revocation

Users disconnect your app from their Connected apps page at any time. Your app can also revoke a token directly:

TerminalCode
curl -X POST https://api.ledgerbee.com/api/v1/oauth/revoke \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "token=REFRESH_OR_ACCESS_TOKEN" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=$LEDGERBEE_CLIENT_SECRET"

The response is 200 with an empty body whether the token was valid, already revoked, or unknown. Only a client-authentication failure returns 401.

Errors

ConditionWhat you observeFix
Unknown or revoked client_id, or unregistered redirect_uriError page, no redirectCheck both against your registration
response_type isn't codeerror=unsupported_response_typeSend response_type=code
Missing PKCE, or a method other than S256error=invalid_requestSend PKCE S256
Scope outside your registered set, or an unknown nameerror=invalid_scopeRequest only registered scopes, or email us to add one
User declineserror=access_deniedNothing to fix
Bad, expired or replayed code, PKCE mismatch, redirect_uri mismatch400 invalid_grantRestart the flow
Wrong client_id / client_secret401 invalid_clientCheck your credentials
Refresh token used, unknown, or grant revoked400 invalid_grant; a reused token revokes the grantReconnect
Token missing a required scope403, error="insufficient_scope"Reconnect with the broader scope
Tenant disconnected your app401, error="invalid_token", details.reason: "grant_revoked"Reconnect

Discovery

Code
GET https://api.ledgerbee.com/api/.well-known/oauth-authorization-server

Returns the OAuth 2.1 metadata: authorization_endpoint, token_endpoint, revocation_endpoint, jwks_uri, supported grant types and scopes. The API Reference carries the full schema, including the JWKS and protected-resource variants.

Last modified on September 13, 2026
AuthenticationAPI Keys
On this page
  • Registration
  • Authorize
  • Consent
  • Token exchange
  • Refresh
  • Using the access token
  • Scopes
  • Revocation
  • Errors
  • Discovery
JSON