Skip to content

Admin API

/admin/v1 is TenkeyBridge's provisioning control plane — the API you automate against to create realms, register OAuth clients, issue agent tokens, and manage API keys, without emailing anyone. It's versioned independently of the QBO-compatible surface and public: integrators are expected to script against it.

It is deliberately not the QBO surface. Errors here look nothing like a QBO Fault and nothing like the RFC 6749 error_description shape /oauth2/v1 uses — see Errors below. Confusing the two shapes would misrepresent what a caller is talking to.

Where this fits

There's also a self-serve portal UI — a browser in front of exactly these endpoints, for anyone who'd rather click through sign-up, org/realm/client setup, and API-key minting than script it.

Authentication

Two credentials are accepted, resolved in this order:

  1. An API key, in the x-api-key header. Minted by POST /admin/v1/api-keys (session only — see Things that are shown once), prefixed tkb_, scoped to exactly one organization for its whole life.
  2. A session cookie — what the portal sends, issued by signing in at /admin/v1/auth.

During early access, the entire /admin/v1 surface also sits behind HTTP Basic auth (ADMIN_GATE_USER/ADMIN_GATE_PASSWORD) as a stopgap until the portal ships its own account-signup flow. A valid x-api-key satisfies this Basic gate on its own — no -u flag needed — except in front of /admin/v1/auth/* (sign-in, sign-up, OAuth callbacks, magic links), which stays Basic-gated unconditionally. An API key must never be a way into the sign-up surface this gate exists to hide.

An API key cannot mint another API keyPOST /admin/v1/api-keys and DELETE /admin/v1/api-keys/:id require a session. A leaked key that could re-mint itself would outlive any attempt to revoke it.

API keys are also rate-limited: 120 requests/minute, per key. Exceeding it returns 429 with { "error": "rate_limited", "message": "..." }.

Organizations and roles

Every realm, OAuth client, agent token, and API key belongs to an organization, never to an individual user. A user reaches an org through membership, at one of three roles:

RoleCan do
ownerEverything
adminCreate/revoke realms, clients, and agent tokens; invite members; issue/revoke API keys
memberRead-only: list realms, clients, members, API keys

An API key's permissions are its creator's current role, re-resolved from the org's member table on every request — not baked into the key at issue time. Demote or remove the creator from the org and every key they made is de-fanged immediately, with no separate revocation step.

GET /admin/v1/orgs lists the organizations the caller belongs to, with their role in each:

bash
curl -s https://api.tenkeybridge.com/admin/v1/orgs \
  -H "x-api-key: $TKB_API_KEY"
json
{
  "orgs": [
    { "id": "org_abc123", "name": "ExampleCo", "slug": "exampleco", "role": "owner" }
  ]
}

An API-key principal only ever sees the one org its key is scoped to, even if the key's creator belongs to others.

GET /admin/v1/orgs/:orgId/members (any member) lists the org's members:

bash
curl -s https://api.tenkeybridge.com/admin/v1/orgs/org_abc123/members \
  -H "x-api-key: $TKB_API_KEY"
json
{
  "members": [
    {
      "id": "mem_1",
      "userId": "usr_1",
      "email": "owner@exampleco.example",
      "name": "Pat Owner",
      "role": "owner",
      "createdAt": "2026-08-01T00:00:00.000Z"
    }
  ]
}

Endpoints

Every endpoint below requires org membership at the listed role (orgId is either a body field on create, a query parameter on list, or resolved server-side from the path resource on everything else).

Realms

POST /admin/v1/realms (admin) — create a realm (tenant) under an org.

bash
curl -s -X POST https://api.tenkeybridge.com/admin/v1/realms \
  -H "x-api-key: $TKB_API_KEY" -H "content-type: application/json" \
  -d '{"orgId": "org_abc123", "name": "Acme Plumbing"}'
json
{
  "realm": {
    "id": "9130351234567890",
    "name": "Acme Plumbing",
    "orgId": "org_abc123",
    "createdAt": "2026-08-10T00:00:00.000Z"
  }
}

realm.id is a QBO-shaped realm id ([1-9][0-9]{15}) — the same realmId you'll pass to /oauth2/v1/authorize and use in every /v3/company/{realmId}/... call.

GET /admin/v1/realms?orgId=... (member) — list an org's realms.

GET /admin/v1/realms/:realmId (member) — realm detail, including agent connectivity and its agent tokens (never the token secrets themselves):

bash
curl -s https://api.tenkeybridge.com/admin/v1/realms/9130351234567890 \
  -H "x-api-key: $TKB_API_KEY"
json
{
  "realm": {
    "id": "9130351234567890",
    "name": "Acme Plumbing",
    "orgId": "org_abc123",
    "createdAt": "2026-08-10T00:00:00.000Z"
  },
  "agent": { "online": true },
  "agentTokens": [
    {
      "id": "tok_1",
      "label": "front office",
      "createdAt": "2026-08-10T00:00:00.000Z",
      "lastUsedAt": null,
      "revoked": false,
      "revokedAt": null
    }
  ]
}

POST /admin/v1/realms/:realmId/agent-tokens (admin, optional { "label": "..." } body) — issue an agent token for that realm, for the edge agent's appsettings.json.

bash
curl -s -X POST https://api.tenkeybridge.com/admin/v1/realms/9130351234567890/agent-tokens \
  -H "x-api-key: $TKB_API_KEY" -H "content-type: application/json" \
  -d '{"label": "front office"}'
json
{ "id": "tok_1", "label": "front office", "token": "tkba_..." }

DELETE /admin/v1/agent-tokens/:tokenId (admin) — revoke an agent token. 204 on success. This is a top-level route, not nested under a realm — you don't need to know a token's realm to revoke it by id.

Realms cannot be deleted in v1DELETE /admin/v1/realms/:realmId 404s. A QuickBooks company is real-world state (invoices, customers, years of history) that outlives any particular integration; there is no safe "delete a company" operation to expose here, and nothing else in the product needs one yet.

OAuth clients

POST /admin/v1/clients (admin) — register an OAuth client.

bash
curl -s -X POST https://api.tenkeybridge.com/admin/v1/clients \
  -H "x-api-key: $TKB_API_KEY" -H "content-type: application/json" \
  -d '{"orgId": "org_abc123", "name": "ExampleCo", "redirectUris": ["https://app.exampleco.example/cb"]}'
json
{
  "client": {
    "id": "tkbc_...",
    "name": "ExampleCo",
    "redirectUris": ["https://app.exampleco.example/cb"],
    "orgId": "org_abc123",
    "createdAt": "2026-08-10T00:00:00.000Z"
  },
  "clientSecret": "tkbs_..."
}

redirectUris is a non-empty array (max 10) of absolute URLs, each validated strictly: https required, except for loopback hosts (localhost, 127.0.0.1, [::1]), which may use plain http for local development; no userinfo; no fragment; no * wildcard; and the value must already be in normalized form (the exact string new URL(...) would produce — no default port, no trailing whitespace, no embedded tab/newline) since the authorize-time matcher compares byte-for-byte. A rejected URI returns 400 invalid_request naming what's wrong.

GET /admin/v1/clients?orgId=... (member) — list an org's clients. Never includes clientSecret.

PATCH /admin/v1/clients/:clientId (admin, { "redirectUris": [...] }) — replace a client's redirect URIs (same validation as create). This is a full replacement, not a merge.

DELETE /admin/v1/clients/:clientId (admin) — delete an OAuth client. 204 on success. This deletes the client's authorization codes and access/refresh tokens in the same transaction — every company connected through that client is disconnected immediately. There's no cascade-free "just deregister the app" option: a token whose issuing client no longer exists is not a credential TenkeyBridge is willing to keep honoring.

API keys

POST /admin/v1/api-keys (admin, session only — an API key cannot call this).

bash
curl -s -X POST https://api.tenkeybridge.com/admin/v1/api-keys \
  -H "cookie: $SESSION_COOKIE" -H "content-type: application/json" \
  -d '{"orgId": "org_abc123", "name": "provisioning"}'
json
{
  "id": "key_1",
  "name": "provisioning",
  "orgId": "org_abc123",
  "key": "tkb_...",
  "createdAt": "2026-08-10T00:00:00.000Z"
}

name is at most 32 characters. orgId is baked into the key's metadata for its whole life — a key is never re-scoped to a different org.

GET /admin/v1/api-keys?orgId=... (member) — list an org's keys. Never includes the key value; a key made by someone who has since left the org is still listed (but no longer authorizes anything — see above) until it's explicitly revoked.

DELETE /admin/v1/api-keys/:id (admin, session only) — revoke a key immediately. 204 on success.

Errors

Every error on this surface is the same two-field shape:

json
{ "error": "unauthenticated", "message": "..." }
errorHTTP statusMeaning
unauthenticated401No session and no valid x-api-key.
forbidden403Authenticated, but your role in this org is too low for the action.
invalid_request400Malformed body, missing required field, or a redirect URI that fails validation.
not_found404No such resource — or a resource that belongs to an organization you're not a member of.
rate_limited429An API key crossed 120 requests/minute.
internal_error500Unexpected server error.

A resource belonging to another organization and a resource that doesn't exist return the byte-identical not_found. This is deliberate: a distinguishable 403 ("yes, that realm exists, you just can't see it") would let a caller enumerate which realm ids, client ids, org ids, and agent-token ids exist by brute-forcing responses. not_found is the one answer that confirms nothing.

Things that are shown once

Three kinds of secret are returned exactly once, at creation, and never again:

  • OAuth client secrets (clientSecret on POST /clients)
  • Agent tokens (token on POST /realms/:realmId/agent-tokens)
  • API keys (key on POST /api-keys)

Every list/detail endpoint that returns the parent resource omits the secret entirely — there is no endpoint, anywhere on this surface, that can hand one back to you a second time. Store it when it's issued, or issue a new one.

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.