Reference
API reference
Generated from the Tabbio API source, so an endpoint listed here exists and an endpoint that exists is listed here. Version 2.0.0.
Base URL
Every request goes to https://server.tabbio.com. The consent screen is the one exception: it is a browser page on https://app.tabbio.com/oauth/authorize, because a person has to see it.
Point a standard OAuth client at https://server.tabbio.com/.well-known/oauth-authorization-server and every endpoint below is configured for you.
Response envelope
Every JSON response carries the same three keys. Read data on success and error on failure. meta holds paging information on list endpoints and is null elsewhere.
{
"data": { "id": "usr_2f8a", "name": "Sara Ahmed" },
"error": null,
"meta": null
}{
"data": null,
"error": {
"code": "OAUTH_INSUFFICIENT_SCOPE",
"message": "This token does not carry the cv:read scope."
},
"meta": null
}Four things are deliberately not wrapped: POST /oauth/token and POST /oauth/introspect, which answer the flat bodies RFC 6749 and RFC 7662 define, the discovery document, where RFC 8414 does, and the PDF endpoints, which stream a file. POST /oauth/revoke keeps the envelope, because RFC 7009 specifies no success body of its own. The field tables on the endpoint pages already show the unwrapped shape, so what you read there is what sits inside data.
Pagination
Every list endpoint is page based. Pass page (1 based, defaults to 1) and pageSize (1 to 100, defaults to 25). There are no cursors anywhere in this API.
{
"data": [ /* one page of records */ ],
"error": null,
"meta": { "page": 1, "pageSize": 25, "total": 63, "totalPages": 3 }
}Stop when page reaches totalPages, or when a page comes back shorter than pageSize. Records are ordered newest first, so a job posted while you are paging shifts later records by one; if you are reconciling rather than browsing, key on the record id rather than the position.
Authentication
Both schemes are HTTP bearer tokens. Which one you use depends on whether a person is involved.
User access token
A user access token (`tbo_at_...`) obtained through Sign in with Tabbio. Send it as `Authorization: Bearer <token>`.
Authorization: Bearer tbo_at_5Kd9xQ2mVr7tN0pLzY4bHwR3jS8cF1gUAPI key
A server-to-server API key (`tbo_sk_...`) created in Tabbio under Settings, Developer. Send it as `Authorization: Bearer <key>`. Available scopes: `companies:read`, `companies:write`, `jobs:read`, `jobs:write`, `candidates:read`.
Authorization: Bearer tbo_sk_9Fh3xK7mQr2tV0nLzB6yWd4Sc8Jg1Up5Rt7Every credential is prefixed, which is what lets a secret scanner recognise one in a commit.
| Prefix | Credential | Notes |
|---|---|---|
tbo_ci_ | Client id | Public. Safe to ship in a browser or a mobile app. |
tbo_cs_ | Client secret | Server side only. Shown once when you create or rotate it. |
tbo_sk_ | API key | Server side only. Carries company scopes, never user scopes. |
tbo_ac_ | Authorization code | Single use, valid for 10 minutes. |
tbo_at_ | Access token | Valid for one hour. |
tbo_rt_ | Refresh token | Valid for 90 days and rotated on every use. |
Scopes
A user grants scopes on the consent screen. They may grant fewer than you asked for, so read the scope field on the token response rather than assuming you got what you requested.
| Scope | Grants |
|---|---|
profile | Name, username, headline, language and public profile link. The photo is only shared if the user allows it. |
email | The verified email address on the Tabbio account. |
cv:read | The CV the user chose to share, kept up to date automatically, as structured data and as a PDF. |
cv:contact | Phone number, full address, date of birth, nationality and similar personal details from that CV. |
API keys carry company scopes instead. They are chosen when you create the key and never change.
| Scope | Grants |
|---|---|
companies:read | List and read the companies your app manages. |
companies:write | Create company pages and update their details. |
jobs:read | List and read jobs at those companies. |
jobs:write | Create, publish, update, close and archive jobs. |
candidates:read | List applicants and read one application in full. |
Errors
The code is stable and safe to branch on. The message is written for a human reading a log and can change at any time.
| Code | HTTP | Meaning |
|---|---|---|
OAUTH_INVALID_REQUEST | 400 | A required parameter is missing or malformed. |
OAUTH_INVALID_CLIENT | 401 | Unknown client id, or client authentication failed. |
OAUTH_INVALID_GRANT | 400 | The code or refresh token is expired, already used, issued to another client, or the PKCE verifier does not match. |
OAUTH_INVALID_SCOPE | 400 | You asked for a scope the app is not allowed to request, or tried to widen a grant on refresh. |
OAUTH_UNAUTHORIZED_CLIENT | 400 | The app may not use this grant type. |
OAUTH_UNSUPPORTED_GRANT_TYPE | 400 | Only authorization_code and refresh_token exist. |
OAUTH_ACCESS_DENIED | 403 | The user declined on the consent screen. |
OAUTH_INVALID_TOKEN | 401 | The access token is missing, expired, or revoked. |
OAUTH_INSUFFICIENT_SCOPE | 403 | The token is valid but the user did not grant the scope this endpoint needs. |
API_KEY_INVALID | 401 | The API key is missing, expired, or revoked. |
API_KEY_INSUFFICIENT_SCOPE | 403 | The key is valid but does not carry the scope this endpoint needs. |
COMPANY_NOT_MANAGED_BY_APP | 403 | The company, job or application belongs to a company your app did not create. |
PUBLIC_API_VALIDATION_ERROR | 400 | The body failed validation, or a job did not satisfy the publish contract. The message names the field. |
JOB_NOT_FOUND | 404 | No job with this id. |
APPLICATION_NOT_FOUND | 404 | No application with this id. |
CV_NOT_FOUND | 404 | The account has no CV to share yet. |
CV_LINK_NOT_FOUND | 404 | The durable CV link is unknown, was revoked, or the user disconnected the app. |
CONNECTION_NOT_FOUND | 404 | The connection behind this token is gone. |
DEVELOPER_PLATFORM_DISABLED | 404 | The developer platform is switched off for this deployment. |
RATE_LIMIT_EXCEEDED | 429 | You are over the per-minute limit for your developer account. meta.retryAfter is the seconds to wait. |
QUOTA_EXCEEDED | 429 | You have spent the daily quota for your developer account. meta.retryAfter counts down to the next UTC midnight. |
DEVELOPER_ACCOUNT_SUSPENDED | 403 | The account is suspended, so every app it owns is refused. Mail developers@tabbio.com. |
The errors guide covers what to do about each one.
Rate limits
Over the limit answers 429. Back off and retry: a burst limit clears within seconds. Partner API calls are counted against your app, so every key you own shares one budget; user token calls are counted against the connection; and the OAuth endpoints are counted against your client id, falling back to the client IP when a request names no client.
| Surface | Burst | Sustained | Counted against |
|---|---|---|---|
/oauth/token, /oauth/revoke, /oauth/introspect | 20 requests per 10 seconds | 1200 per hour | Your client id, falling back to the client IP |
/v1/* with an API key | 20 requests per second | 3000 per minute | Your app |
/v1/me* with an access token | 20 requests per second | 3000 per minute | The connection |
GET /v1/cv-links/{token} | 60 requests per second | 2000 per minute | Client IP |
Versioning
The partner API is versioned in the path. Everything under /v1 keeps its shape: fields are added, never removed or retyped, and a new field can appear in a response at any time, so parse leniently. A change that would break a client ships as /v2 alongside /v1.
This page describes version 2.0.0 of the document. https://server.tabbio.com/v1/openapi.json serves the live version, which is what an SDK generator or a coding agent should read.
OAuth endpoints are not versioned, because they implement published standards rather than a Tabbio shape.