Partner API

Companies

Create company pages your app manages, keep the create idempotent with your own identifiers, and page through the list.

A company on Tabbio is a public career page with a name, a logo, a description and its jobs. Jobs belong to companies, so this is the first call in a partner integration.

Everything here needs an API key. See API keys if you do not have one yet.

Create a company

POSThttps://server.tabbio.com/v1/companies

Requires companies:write.

Terminal
curl -X POST https://server.tabbio.com/v1/companies \
  -H "Authorization: Bearer $TABBIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Northwind Logistics",
    "industry": "Logistics",
    "employeeCount": "51-200",
    "location": "Dubai, United Arab Emirates",
    "description": "Northwind Logistics moves temperature controlled freight across the Gulf, with a fleet of 120 vehicles and warehouses in Dubai and Riyadh.",
    "website": "https://northwind.example",
    "externalId": "crm-4412"
  }'
FieldTypeDescription
namerequiredstring

2 to 120 characters. Shown on the career page and on every job.

industryrequiredstring

Free text, up to 80 characters, for example Logistics or Financial services.

employeeCountrequiredstring

A band rather than a number, for example 51-200.

locationrequiredstring

City and country as one line.

descriptionrequiredstring

At least 50 characters, at most 4000. It appears on the public page, so a one-line stub is rejected.

slugstring

The career-page URL segment: lowercase letters, digits and hyphens, 3 to 48 characters. Derived from the name when omitted, with a suffix if it is taken.

websitestring

Absolute URL, or an empty string to clear it.

logostring

Public URL of a square image. Tabbio downloads it and stores its own copy, so a link that later expires is fine.

externalIdstring

Your own identifier for this company. Makes creates idempotent.

201 Created
{
  "data": {
    "id": "cmp_4k8m2n6p",
    "name": "Northwind Logistics",
    "slug": "northwind-logistics",
    "tagline": null,
    "description": "Northwind Logistics moves temperature controlled freight across the Gulf, with a fleet of 120 vehicles and warehouses in Dubai and Riyadh.",
    "location": "Dubai, United Arab Emirates",
    "website": "https://northwind.example",
    "industry": "Logistics",
    "employeeCount": "51-200",
    "founded": null,
    "logo": null,
    "banner": null,
    "published": true,
    "verified": false,
    "externalId": "crm-4412",
    "createdAt": "2026-09-01T08:00:00.000Z",
    "updatedAt": "2026-09-01T08:00:00.000Z"
  },
  "error": null,
  "meta": null
}

description has a floor

Anything under 50 characters is refused with PUBLIC_API_VALIDATION_ERROR. It is the single most common failure on this endpoint, and it exists because the value is the public career page's only prose.

Idempotency with externalId

externalId is your own identifier: the primary key in your CRM, the account number, whatever you already have. Send it and the create becomes idempotent.

  • First call with a given externalId creates the company and returns 201.
  • Any repeat returns the existing company unchanged, with 200.

That means a retry after a timeout is safe, and a sync job can call create for every company on every run without producing duplicates.

sync.js
// Safe to run repeatedly. The first pass creates, later passes read back.
for (const account of await crm.listAccounts()) {
  const response = await fetch("https://server.tabbio.com/v1/companies", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.TABBIO_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ ...toTabbioCompany(account), externalId: account.id }),
  });

  const { data: company } = await response.json();
  await crm.saveTabbioId(account.id, company.id);
}

A repeat does not update

Sending different fields with an externalId you have already used returns the company as it is. It does not merge your changes. Use PATCH for that.

externalId is unique per app, so two different apps can both use crm-4412 without colliding.

Read a company

GEThttps://server.tabbio.com/v1/companies/{companyId}

Requires companies:read.

Terminal
curl https://server.tabbio.com/v1/companies/$COMPANY_ID \
  -H "Authorization: Bearer $TABBIO_API_KEY"

Update a company

PATCHhttps://server.tabbio.com/v1/companies/{companyId}

Requires companies:write. Only the fields you send change.

Terminal
curl -X PATCH https://server.tabbio.com/v1/companies/$COMPANY_ID \
  -H "Authorization: Bearer $TABBIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tagline": "Cold chain across the Gulf", "employeeCount": "201-500" }'

name, tagline, description, location, website, industry, employeeCount, logo and socials are updatable.

slug is not, and neither is founded. The career-page URL is a public handle that other Tabbio surfaces link to, so changing it would break every published job URL and every link anyone has shared.

List companies

GEThttps://server.tabbio.com/v1/companies

Requires companies:read. Newest first. Only companies your app created appear; companies made by another app, or by an employer inside the Tabbio app, are never returned.

FieldTypeDescription
pageinteger

1 based page number. Defaults to 1.

pageSizeinteger

Items per page, 1 to 100. Defaults to 25.

200 OK
{
  "data": [{ "id": "cmp_4k8m2n6p", "name": "Northwind Logistics", "slug": "northwind-logistics" }],
  "error": null,
  "meta": { "page": 1, "pageSize": 25, "total": 63, "totalPages": 3 }
}
list-all.js
async function listAllCompanies() {
  const all = [];
  let page = 1;
  let totalPages = 1;

  do {
    const url = new URL("https://server.tabbio.com/v1/companies");
    url.searchParams.set("page", String(page));
    url.searchParams.set("pageSize", "100");

    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.TABBIO_API_KEY}` },
    });
    const { data, meta } = await response.json();

    all.push(...data);
    totalPages = meta.totalPages;
    page += 1;
  } while (page <= totalPages);

  return all;
}

Ownership

A company created through the API is owned by the Tabbio user who owns the app, and is linked to the app. Two consequences worth knowing:

  • Your app can read and change it. Nobody else's app can, and reading one you do not manage answers 403 with COMPANY_NOT_MANAGED_BY_APP.
  • It shows up in that owner's employer surfaces inside Tabbio, so a human can look at what the integration created.

There is no way to hand a company over to another app or to un-manage one. Deleting the app removes the link.