Documentation
API v1
Profiles
Your org directory and org chart, with cursor pagination and incremental sync. Names, titles, departments, teams, locations, start dates, and manager relationships.
Working with an AI agent?or point it at the Profiles markdown or the full-API markdown.
List profiles
GET
/api/v1/profilesYour org directory and org chart as a paginated table: names, titles, departments, teams, locations, employment start dates, and manager relationships. Returns only current, non-hidden people, and defaults to Active so a COUNT is a true headcount (pass ?status=Active,Pending to include people who have been invited but have not joined). Requires profile:read (or profile:write). Account state (type, status, invited, joined) lives on /api/v1/users; the two join on userId.
| Parameter | Type | Description |
|---|---|---|
| cursor | string optional | Opaque cursor from a previous response's "nextCursor". Omit for the first page. |
| limit | number optional | Rows per page, 1-500. Defaults to 100. |
| updatedSince | string optional | ISO 8601 timestamp (or epoch milliseconds). Returns only profiles changed since this time, and populates "removed" with anyone who left the directory. Pass back the "syncedThrough" value from your previous run. |
| type | User | Agent | Pet | all optional | Profile type, or a comma-separated list, or "all". One of: User, Agent, Pet. Defaults to User so a COUNT of the response is a true headcount and never includes AI agent or pet profiles. |
| status | Active | Pending optional | Account status, or a comma-separated list. One of: Active, Pending. Defaults to Active — the common case is a headcount of people who are actually here, so someone invited but not yet joined is excluded. Pass "Active,Pending" for the full directory. Suspended and inactive people are never in this resource; they are on /api/v1/users. |
Response:
{
data: {
id: number; // stable key; managerId points into this same namespace
userId: number; // join key to GET /api/v1/users
email: string;
firstName: string | null;
lastName: string | null;
displayName: string;
avatarUrl: string | null;
pronouns: string | null;
pronunciation: string | null;
type: 'User' | 'Agent' | 'Pet';
title: string | null;
department: string | null;
team: string | null;
division: string | null;
organization: string | null;
costCenter: string | null;
orgUnit: string | null;
employeeId: string | null;
startDate: string | null; // EMPLOYMENT start, ISO 8601. Not account creation.
seniority: number | null; // tenure percentile
location: string | null;
city: string | null;
state: string | null;
country: string | null;
timezone: string | null;
tagline: string | null;
status: string | null;
phone: string | null;
birthday: string | null;
socialUrls: object | null;
managerId: number | null; // self-join: managerId = id
managerEmail: string | null;
updated: string | null; // ISO 8601
}[];
removed: number[]; // ids that left the directory; only when updatedSince is set
nextCursor: string | null;
hasMore: boolean;
syncedThrough: string; // ISO 8601; pass back as updatedSince
}Get a profile
GET
/api/v1/profiles/getOne profile by id or email, with its full upward manager chain and the values of its trading card and custom fields. Unlike the list, this returns deactivated and hidden people too, so it can be used to reconcile a departure. Requires profile:read.
| Parameter | Type | Description |
|---|---|---|
| id | number optional | The profile id. Either "id" or "email" is required. |
string optional | The profile email. Either "id" or "email" is required. |
Response:
{
// every field from GET /api/v1/profiles, plus:
managerChain: { id: number; email: string; displayName: string; depth: number }[];
// The trading card and custom field values, by field name ("field_<id>").
// An empty value or a disabled field is absent.
fields: Record<string, string>;
}List profile fields
GET
/api/v1/profiles/fieldsYour org's profile fields: the built-in fields, the trading card fields, and the custom fields, in one list, as the profile settings page shows them. Use it to discover which fields exist before writing to them. Requires profile:read.
Response:
{
data: {
// The key in "fields" of POST /api/v1/profiles/update: a built-in name
// such as "city", or "field_<id>" for a trading card or custom field.
name: string;
label: string;
type: string;
// For example "basics", "tradingCardFront", "tradingCardBack", "custom".
group: string;
groupLabel: string;
kind: 'BuiltIn' | 'Org';
enabled: boolean;
userEditable: boolean;
}[];
}Update a profile
POST
/api/v1/profiles/updateUpdate profile fields for one person. Requires profile:write, which only an Administrator or Owner can grant, or profile:write:self, which any member can grant and which updates only the token owner's profile. A token whose owner is not an admin updates only its owner's profile, and only the fields with "userEditable" true.
| Parameter | Type | Description |
|---|---|---|
string required | The email of the person to update. | |
| fields | object required | Map of profile field name to value (see GET /api/v1/profiles/fields for valid names). A trading card or custom field is "field_<id>", and its value is a string. An empty string clears it. |
Response:
{
id: number;
email: string;
updated: boolean;
}