Skip to content

Authentication

TenkeyBridge clones the QuickBooks Online OAuth 2.0 authorization-code flow. If your app already connects to QBO, you keep your OAuth code and swap two URLs.

QuickBooks OnlineTenkeyBridge
Authorize URLhttps://appcenter.intuit.com/connect/oauth2https://api.tenkeybridge.com/oauth2/v1/authorize
Token URLhttps://oauth.platform.intuit.com/oauth2/v1/tokens/bearerhttps://api.tenkeybridge.com/oauth2/v1/tokens

Getting credentials

Credentials are self-serve through the Admin API:

  • Register an OAuth clientPOST /admin/v1/clients with your app's name and redirect URIs. Returns a client_id and a client_secret, shown once.
  • Provision a realm per customerPOST /admin/v1/realms, one per QuickBooks company you connect. Returns a realmId — you already hold this by the time you send anyone to the authorize URL, because you're the one who created it.

Both live under one organization; see Organizations and roles for who on your team can do what.

Early access

The self-serve portal UI in front of this API (#91 P4) isn't live yet — for now, access to /admin/v1 itself is arranged by email (hello@tenkeybridge.com). Once you have that access, provisioning clients and realms works exactly as described above and won't change when the portal ships.

1. Send the user to the authorize URL

text
https://api.tenkeybridge.com/oauth2/v1/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.example.com/callback
  &state=RANDOM_OPAQUE_STRING
  &scope=com.intuit.quickbooks.accounting
  &realm_id=THE_REALM_ID
  • response_type must be code (anything else is rejected with unsupported_response_type, HTTP 400).
  • redirect_uri must exactly match one of your registered redirect URIs.
  • scope is accepted for QBO compatibility and defaults to com.intuit.quickbooks.accounting.
  • realm_id is required: the realmId of the QuickBooks company being connected. You already hold it — you're the one who created the realm through the Admin API — so this is the one parameter Intuit's authorize URL doesn't have.

The consent page names your app and the company (realm_id resolves server-side to a company name) and has one button. On success the browser is redirected to your redirect_uri with code, your state, and realmId in the query string — the same shape Intuit sends.

The server enforces that realm_id belongs to the same organization that owns client_id, on both the GET that renders this page and the POST it submits to — independently, so a value that only made it through a browser round trip is never trusted on its own. With that ownership check server-side, this page is honest about what it is: it preserves the QBO consent-screen shape so your existing OAuth code keeps working unmodified, but the click itself isn't the access control — the same-org check is. A realm_id that doesn't exist, or belongs to a different organization than the client, answers with the identical error either way (see below), so the page can't be used to probe which realm ids exist.

2. Exchange the code for tokens

POST /oauth2/v1/tokens with HTTP Basic auth (client_id:client_secret) and a form body:

bash
curl -s https://api.tenkeybridge.com/oauth2/v1/tokens \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code="$CODE" \
  -d redirect_uri=https://yourapp.example.com/callback

redirect_uri must match the one the code was issued against. A mismatch returns invalid_grant and consumes the code — restart from step 1.

Response:

json
{
  "token_type": "bearer",
  "access_token": "…",
  "expires_in": 3600,
  "refresh_token": "…",
  "x_refresh_token_expires_in": 8640000,
  "realmId": "1234567890"
}

Access tokens last 1 hour; refresh tokens last 100 days.

3. Refresh

Same endpoint, same Basic auth:

bash
curl -s https://api.tenkeybridge.com/oauth2/v1/tokens \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=refresh_token \
  -d refresh_token="$REFRESH_TOKEN"

Refresh tokens rotate: each refresh returns a new pair. Persist the new access_token and refresh_token together before using either.

Rotation is soft, matching Intuit: the previous refresh token stays usable for about 5 minutes after rotation. This exists for apps that refresh concurrently (a scheduled sync job racing a user-triggered one, say) without a refresh mutex — the race's "loser" isn't punished. Presenting the previous token within that window returns a fresh, independently valid pair (not the same one the winner got); both the winner's and the loser's pairs keep working. Presenting it again beyond the window returns invalid_grant. Revoking any token in the chain (see below) kills refresh for the whole connection immediately, window or not.

4. Call the API

bash
curl -s "https://api.tenkeybridge.com/v3/company/$REALM_ID/query" \
  --get --data-urlencode "query=select * from Customer maxresults 5" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Accept: application/json"

Same paths and JSON as QBO — see the Quickstart and the compatibility contract.

Revoking

POST /oauth2/v1/revoke with Basic auth and token=<access-or-refresh-token>. Idempotent — always returns 200 (RFC 7009). Revoking any token for a connection kills refresh for that whole connection right away: a still-in-window previous refresh token (see rotation, above) stops working too. A disconnect means disconnected.

Errors

SymptomMeaning
400 unsupported_response_type on authorizeresponse_type wasn't code
400 "invalid client_id or redirect_uri"Unknown client, or redirect_uri not registered
400 "realm_id is required" on authorizerealm_id missing from the authorize URL or consent form
400 "invalid realm_id" on authorizeThe realm doesn't exist, or belongs to a different organization than the client — same error either way
401 invalid_client on /tokensBasic auth header wrong (client ID/secret)
400 invalid_grant on /tokensCode expired/consumed, redirect_uri mismatch, or a refresh token reused past its ~5-minute grace window (or after revocation)

API-level faults use QBO's Fault shape — see Error codes.

TenkeyBridge is an independent product, not affiliated with, endorsed by, or sponsored by Intuit Inc. QuickBooks, QuickBooks Online, and QuickBooks Desktop are trademarks of Intuit Inc., used only to describe compatibility.

TenkeyBridge is an independent product, not affiliated with, endorsed by, or sponsored by Intuit Inc. QuickBooks, QuickBooks Online, and QuickBooks Desktop are trademarks of Intuit Inc., used only to describe compatibility.