Skip to main content

Quickstart

Five minutes from nothing to a paginated list of employees.

1. Create a key

In the app, go to Settings → API keys and choose Create key. You need the api.manage permission; if the page is not there, ask whoever administers your workspace to grant it.

Pick the narrowest scopes that do the job. employees:read and org:read are enough for everything on this page.

The secret is shown once

The full key is displayed exactly once, at creation. We store a hash, so we cannot show it to you again and cannot recover it — if it is lost, revoke the key and create another. Put it straight into whatever your application uses for secrets.

A key looks like this:

opk_live_QyHoRPMowevteWjvLMV5pRB9jCkkwpzSqUD7cDuKnqyzzBdV

2. Check that it works

/v1/me describes the calling key and touches nothing else, so it is the safest possible first request.

curl https://app.operentra.com/api/v1/me \
-H "Authorization: Bearer $OPERENTRA_API_KEY"

If that returns 200, you are authenticated. If it returns a problem document, the code field says exactly what is wrong — invalid_api_key for a bad key, insufficient_scope if it is valid but not allowed here.

3. Read the workspace

Fetch /v1/company once and keep it. Every money field in the API is in this workspace's currency and every calendar date is in its timezone; without those two values you cannot correctly display anything else.

curl https://app.operentra.com/api/v1/company \
-H "Authorization: Bearer $OPERENTRA_API_KEY"

4. List employees

curl "https://app.operentra.com/api/v1/employees?limit=25" \
-H "Authorization: Bearer $OPERENTRA_API_KEY"
{
"data": [
{
"object": "employee",
"id": "e83f17a0-5771-4369-8a01-50c360d1caca",
"employee_code": "HH-EMP-0001",
"first_name": "Ayesha",
"last_name": "Khan",
"status": "active",
"joining_date": "2024-02-01",
"deleted": false,
"created_at": "2024-02-01T06:12:44.000Z",
"updated_at": "2026-08-19T11:03:10.000Z"
}
],
"has_more": true,
"next_cursor": "MjAyNC0wMi0wMVQwNjoxMjo0NC4wMDBafGU4M2Yx"
}

Note what is not there: no national ID, no salary, no bank account. Those need employees.sensitive:read, and without it they are omitted rather than returned as null — a missing key means "your key may not see this", never "this employee has no such value". See Authentication.

5. Page through the rest

has_more tells you another page exists; pass next_cursor back as starting_after.

curl "https://app.operentra.com/api/v1/employees?limit=25&starting_after=MjAyNC0w..." \
-H "Authorization: Bearer $OPERENTRA_API_KEY"

Do not build the cursor yourself — it is opaque and its contents will change. Pagination explains why this is a cursor and not a page number, and why that matters for a sync that runs while people are still working.

A first sync, in full

const KEY = process.env.OPERENTRA_API_KEY;
const BASE = 'https://app.operentra.com/api/v1';

async function fetchAll(resource, params = {}) {
const out = [];
let cursor = null;

do {
const query = new URLSearchParams({ ...params, limit: '100' });
if (cursor) query.set('starting_after', cursor);

const res = await fetch(`${BASE}/${resource}?${query}`, {
headers: { Authorization: `Bearer ${KEY}` },
});

if (!res.ok) {
// Branch on `code`, never on the prose in `title` or `detail`.
const problem = await res.json();
throw new Error(`${resource} failed: ${problem.code} (${problem.request_id})`);
}

const page = await res.json();
out.push(...page.data);
cursor = page.next_cursor;
} while (cursor);

return out;
}

const employees = await fetchAll('employees');
console.log(`${employees.length} employees`);

Next

  • Writing data? Read Idempotency before your first POST. A timed-out request that you retry without an Idempotency-Key creates a second record, and for employees that means a second person.
  • Syncing on a schedule? Use updated_since rather than refetching everything, and read Webhooks — being told about a change beats asking.
  • Hitting 429s? Rate limits explains the per-plan budget and how to stay inside it.