# OrgOrg API v1 — Profiles — instructions for AI agents

This document describes the Profiles endpoints of the OrgOrg v1 REST API. Fetch it from https://orgorg.com/docs/api/v1/profiles/agents.md. The full API is documented at https://orgorg.com/docs/api/v1/agents.md.

## Basics

- Base URL: `https://orgorg.com`. All endpoint paths below are relative to it.
- Every request requires an `Authorization: Bearer <token>` header.
- `GET` endpoints take parameters in the URL query string. `POST` endpoints take a JSON body with a `Content-Type: application/json` header.
- All responses are JSON. A non-2xx status means the request failed; the body describes the problem.
- Endpoints are stable, but new response fields may be added without notice — ignore fields you do not recognize.

## Getting a token

Tokens are created by a signed-in user at https://orgorg.com/docs/credentials and inherit that user's permissions in their organization. If you do not have a token, ask your user to create one there. Verify a token works with:

```sh
curl "https://orgorg.com/api/v1/auth/test" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN"
```

A valid token returns `{ "ok": true, ... }`.

## Profiles

Your org directory and org chart, with cursor pagination and incremental sync. Names, titles, departments, teams, locations, start dates, and manager relationships. Human-readable docs: https://orgorg.com/docs/api/v1/profiles

### List profiles

`GET /api/v1/profiles`

Your 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.

Parameters:

- `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.

Example request:

```sh
curl -X GET "https://orgorg.com/api/v1/profiles?limit=10" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN"
```

Response shape:

```ts
{
  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/get`

One 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.

Parameters:

- `id` (number, optional) — The profile id. Either "id" or "email" is required.
- `email` (string, optional) — The profile email. Either "id" or "email" is required.

Example request:

```sh
curl -X GET "https://orgorg.com/api/v1/profiles/get?email=user%40domain.com" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN"
```

Response shape:

```ts
{
  // 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/fields`

Your 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.

Parameters: none.

Example request:

```sh
curl -X GET "https://orgorg.com/api/v1/profiles/fields" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN"
```

Response shape:

```ts
{
  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/update`

Update 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.

Parameters:

- `email` (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.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/profiles/update" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@domain.com",
    "fields": {
      "city": "Mountain View",
      "state": "CA"
    }
  }'
```

Response shape:

```ts
{
  id: number;
  email: string;
  updated: boolean;
}
```
