# OrgOrg API v1 — Leaderboards — instructions for AI agents

This document describes the Leaderboards endpoints of the OrgOrg v1 REST API. Fetch it from https://orgorg.com/docs/api/v1/leaderboards/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, ... }`.

## Leaderboards

Create leaderboards that a process fills with counts, with place badges that move by themselves, and voting boards with contenders, votes, and ratings. Human-readable docs: https://orgorg.com/docs/api/v1/leaderboards

### List leaderboards

`GET /api/v1/leaderboards/list`

List the org's leaderboards and voting boards by updatedAt, oldest first, with a cursor. A board that changes during a walk moves after the cursor, thus pass updatedSince to sync only the changes. Requires leaderboards:read (or :write).

Parameters:

- `limit` (number, optional) — Boards per page, 1 to 500. The default is 100.
- `cursor` (string, optional) — The nextCursor of the previous page.
- `updatedSince` (number, optional) — Only boards updated at or after this time, in milliseconds since 1970.
- `kind` (`Voting` | `System` | `Counted` | `Bracket`, optional) — Only boards of this kind.

Example request:

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

Response shape:

```ts
{
  data: {
    id: string;
    name: string;
    emoji: string | null;
    kind: 'Voting' | 'System' | 'Counted' | 'Bracket';
    type: 'FaceOff' | 'UpDown' | 'Rate' | 'Review' | 'Leaderboard' | 'Bracket';
    systemKind: 'ThanksReceived' | 'ThanksGiven' | 'ContactsParsed' | 'CompaniesParsed' | null;
    description: string | null;
    countLabel: string | null;
    badgeMode: 'None' | 'Top1' | 'Top3';
    notifyMode: 'Gains' | 'AllChanges' | 'Silent' | null;
    createdByProfileId: number;
    createdAt: number;
    updatedAt: number;
    lastActivityAt: number;
  }[];
  nextCursor: string | null;
}
```

### Get leaderboard

`GET /api/v1/leaderboards/get`

Get one board with its top 10 rows and its row total. On a read-only leaderboard the rows are entries; on a voting board they are contenders. Requires leaderboards:read (or :write).

Parameters:

- `id` (string, required) — Leaderboard id.

Example request:

```sh
curl -X GET "https://orgorg.com/api/v1/leaderboards/get?id=LEADERBOARD_ID" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN"
```

Response shape:

```ts
{
  data: {
    id: string;
    name: string;
    emoji: string | null;
    kind: 'Voting' | 'System' | 'Counted' | 'Bracket';
    type: 'FaceOff' | 'UpDown' | 'Rate' | 'Review' | 'Leaderboard' | 'Bracket';
    systemKind: 'ThanksReceived' | 'ThanksGiven' | 'ContactsParsed' | 'CompaniesParsed' | null;
    description: string | null;
    countLabel: string | null;
    badgeMode: 'None' | 'Top1' | 'Top3';
    notifyMode: 'Gains' | 'AllChanges' | 'Silent' | null;
    createdByProfileId: number;
    createdAt: number;
    updatedAt: number;
    lastActivityAt: number;
    top: {
      rank: number;
      kind: 'Item' | 'Member' | 'Named';
      id: string;
      name: string;
      imageUrl?: string;
      linkUrl?: string;
      profileId?: number;
      email?: string;
      count?: number;
      score?: number;
      votes?: number;
      ratingCount?: number;
    }[];
    totalRows: number;
  };
}
```

### Create leaderboard

`POST /api/v1/leaderboards/create`

Create a voting board (kind Voting, with a type) or a counted leaderboard that a process fills (kind Counted). The token owner is the creator. Only an admin creates a counted leaderboard with badges. Requires leaderboards:write.

Parameters:

- `kind` (`Voting` | `Counted`, required) — Voting or Counted.
- `name` (string, required) — Name of the board.
- `emoji` (string, optional) — An emoji for the board.
- `type` (`FaceOff` | `UpDown` | `Rate` | `Review`, optional) — Voting boards only: how people vote. The default is FaceOff.
- `description` (string, optional) — Counted leaderboards only: a line about the board.
- `countLabel` (string, optional) — Counted leaderboards only: the noun after a count, for example "pull requests merged".
- `notifyMode` (`Gains` | `AllChanges` | `Silent`, optional) — Counted leaderboards only: who hears about a place change. The default is Gains.
- `badgeMode` (`None` | `Top1` | `Top3`, optional) — Counted leaderboards only: badges for the top places. Needs an admin. The default is None.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/leaderboards/create" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "Counted",
    "name": "Pull requests merged",
    "countLabel": "pull requests",
    "notifyMode": "Gains"
  }'
```

Response shape:

```ts
{
  data: {
    id: string;
    name: string;
    emoji: string | null;
    kind: 'Voting' | 'System' | 'Counted' | 'Bracket';
    type: 'FaceOff' | 'UpDown' | 'Rate' | 'Review' | 'Leaderboard' | 'Bracket';
    systemKind: 'ThanksReceived' | 'ThanksGiven' | 'ContactsParsed' | 'CompaniesParsed' | null;
    description: string | null;
    countLabel: string | null;
    badgeMode: 'None' | 'Top1' | 'Top3';
    notifyMode: 'Gains' | 'AllChanges' | 'Silent' | null;
    createdByProfileId: number;
    createdAt: number;
    updatedAt: number;
    lastActivityAt: number;
  };
  created: true;
}
```

### Update leaderboard

`POST /api/v1/leaderboards/update`

Change a board. A voting board takes name, emoji, and type. A counted leaderboard takes name, emoji, description, countLabel, notifyMode, and badgeMode. An OrgOrg leaderboard takes name, emoji, and badgeMode. A badge mode needs an admin. An omitted field stays the same. Requires leaderboards:write.

Parameters:

- `id` (string, required) — Leaderboard id.
- `name` (string, optional) — New name.
- `emoji` (string, optional) — New emoji.
- `type` (`FaceOff` | `UpDown` | `Rate` | `Review`, optional) — Voting boards only: the new voting style. Each contender gets a new score.
- `description` (string, optional) — Counted leaderboards only. An empty string clears it.
- `countLabel` (string, optional) — Counted leaderboards only. An empty string clears it.
- `notifyMode` (`Gains` | `AllChanges` | `Silent`, optional) — Read-only leaderboards only: who hears about a place change.
- `badgeMode` (`None` | `Top1` | `Top3`, optional) — Read-only leaderboards only: badges for the top places. Needs an admin. The badges move at once.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/leaderboards/update" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "LEADERBOARD_ID",
    "badgeMode": "Top3"
  }'
```

Response shape:

```ts
{
  data: {
    id: string;
    name: string;
    emoji: string | null;
    kind: 'Voting' | 'System' | 'Counted' | 'Bracket';
    type: 'FaceOff' | 'UpDown' | 'Rate' | 'Review' | 'Leaderboard' | 'Bracket';
    systemKind: 'ThanksReceived' | 'ThanksGiven' | 'ContactsParsed' | 'CompaniesParsed' | null;
    description: string | null;
    countLabel: string | null;
    badgeMode: 'None' | 'Top1' | 'Top3';
    notifyMode: 'Gains' | 'AllChanges' | 'Silent' | null;
    createdByProfileId: number;
    createdAt: number;
    updatedAt: number;
    lastActivityAt: number;
  };
}
```

### Delete leaderboard

`POST /api/v1/leaderboards/delete`

Delete a board with its entries, contenders, votes, and ratings. A voting board: its creator. A counted leaderboard: its creator or an admin, and only an admin when it awards badges; its badges close. An OrgOrg leaderboard: an admin. Requires leaderboards:write.

Parameters:

- `id` (string, required) — Leaderboard id.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/leaderboards/delete" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "LEADERBOARD_ID"
  }'
```

Response shape:

```ts
{
  data: { id: string };
  deleted: true;
}
```

### List entries

`GET /api/v1/leaderboards/entries/list`

List the entries of a read-only leaderboard in rank order: the highest count first, then the entry that reached its count first. A member the org suspended is left out, thus a page can hold fewer entries than the limit. Requires leaderboards:read (or :write).

Parameters:

- `id` (string, required) — Leaderboard id.
- `limit` (number, optional) — Entries per page, 1 to 500. The default is 100.
- `cursor` (string, optional) — The nextCursor of the previous page.

Example request:

```sh
curl -X GET "https://orgorg.com/api/v1/leaderboards/entries/list?id=LEADERBOARD_ID&limit=100" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN"
```

Response shape:

```ts
{
  data: {
    rank: number;
    id: string;
    kind: 'Member' | 'Named';
    key: string; // member:<profileId> for a member
    count: number;
    countChangedAt: number;
    name: string;
    imageUrl: string | null;
    linkUrl: string | null;
    profileId: number | null;
    email: string | null;
  }[];
  nextCursor: string | null;
}
```

### Upsert entries

`POST /api/v1/leaderboards/entries/upsert`

Create or update entries of a counted leaderboard by key, up to 500 per request. A key is trimmed and in lowercase. An entry not in the request keeps its row. The answer has one result per entry, and one bad entry does not stop the others. An identical request writes nothing and answers Unchanged for each entry. On a board with badges, only an admin sends entries, and the badges move 5 minutes after the order changes. Requires leaderboards:write.

Parameters:

- `id` (string, required) — Leaderboard id.
- `entries` (object, required) — An array of entries. A member: { member: { email } or { profileId }, count }. A named entry: { key, name, imageUrl?, linkUrl?, count }. A count is a whole number, 0 or more.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/leaderboards/entries/upsert" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "LEADERBOARD_ID",
    "entries": [
      {
        "member": {
          "email": "sam@example.com"
        },
        "count": 12
      },
      {
        "key": "team-a",
        "name": "Team A",
        "count": 9
      }
    ]
  }'
```

Response shape:

```ts
{
  data: {
    results: {
      index: number;
      key: string | null;
      status: 'Created' | 'Updated' | 'Unchanged' | 'Deleted' | 'NotFound' | 'Rejected';
      error?: string;
    }[];
    orderChanged: boolean;
  };
}
```

### Replace entries

`POST /api/v1/leaderboards/entries/replace`

Make a counted leaderboard hold exactly these entries, 1 to 1000. A row not in the request is deleted, except the row of an entry that the request sends with an error. Requires leaderboards:write.

Parameters:

- `id` (string, required) — Leaderboard id.
- `entries` (object, required) — An array of entries. A member: { member: { email } or { profileId }, count }. A named entry: { key, name, imageUrl?, linkUrl?, count }. A count is a whole number, 0 or more.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/leaderboards/entries/replace" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "LEADERBOARD_ID",
    "entries": [
      {
        "key": "team-a",
        "name": "Team A",
        "count": 10
      },
      {
        "key": "team-b",
        "name": "Team B",
        "count": 7
      }
    ]
  }'
```

Response shape:

```ts
{
  data: {
    results: {
      index: number;
      key: string | null;
      status: 'Created' | 'Updated' | 'Unchanged' | 'Deleted' | 'NotFound' | 'Rejected';
      error?: string;
    }[];
    orderChanged: boolean;
    deleted: number;
  };
}
```

### Delete entries

`POST /api/v1/leaderboards/entries/delete`

Delete entries of a counted leaderboard by key or by member, up to 1000. A member in any status can be removed. Requires leaderboards:write.

Parameters:

- `id` (string, required) — Leaderboard id.
- `entries` (object, required) — An array of { key } or { member: { email } or { profileId } }.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/leaderboards/entries/delete" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "LEADERBOARD_ID",
    "entries": [
      {
        "key": "team-b"
      }
    ]
  }'
```

Response shape:

```ts
{
  data: {
    results: {
      index: number;
      key: string | null;
      status: 'Created' | 'Updated' | 'Unchanged' | 'Deleted' | 'NotFound' | 'Rejected';
      error?: string;
    }[];
    orderChanged: boolean;
  };
}
```

### List contenders

`GET /api/v1/leaderboards/contenders/list`

List the contenders of a voting board, best score first, then the most rated. The score is the Elo rating on a face-off board, the net vote on an up/down board, and the average stars on a star board. Requires leaderboards:read (or :write).

Parameters:

- `id` (string, required) — Leaderboard id.

Example request:

```sh
curl -X GET "https://orgorg.com/api/v1/leaderboards/contenders/list?id=LEADERBOARD_ID" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN"
```

Response shape:

```ts
{
  data: ({
    id: string;
    leaderboardId: string;
    name: string;
    description: string | null;
    imageUrl: string | null;
    linkUrl: string | null;
    mapsUrl: string | null;
    score: number; // the Elo rating, the net vote, or the average stars
    eloRating: number;
    matchupVotes: number;
    ratingSum: number;
    ratingCount: number;
    addedByProfileId: number;
    createdAt: number;
  } & { rank: number })[];
}
```

### Add contender

`POST /api/v1/leaderboards/contenders/create`

Add a contender to a voting board. A board holds at most 500 contenders. Requires leaderboards:write.

Parameters:

- `id` (string, required) — Leaderboard id.
- `name` (string, required) — Name of the contender.
- `description` (string, optional) — A short description.
- `imageUrl` (string, optional) — An image URL.
- `linkUrl` (string, optional) — A link URL.
- `mapsUrl` (string, optional) — A Google Maps URL.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/leaderboards/contenders/create" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "LEADERBOARD_ID",
    "name": "Boba Guys"
  }'
```

Response shape:

```ts
{
  data: {
    id: string;
    leaderboardId: string;
    name: string;
    description: string | null;
    imageUrl: string | null;
    linkUrl: string | null;
    mapsUrl: string | null;
    score: number; // the Elo rating, the net vote, or the average stars
    eloRating: number;
    matchupVotes: number;
    ratingSum: number;
    ratingCount: number;
    addedByProfileId: number;
    createdAt: number;
  };
  created: true;
}
```

### Update contender

`POST /api/v1/leaderboards/contenders/update`

Change a contender. The person who added it or the board creator. An omitted field stays the same, and an empty string clears an optional field. Requires leaderboards:write.

Parameters:

- `id` (string, required) — Contender id.
- `name` (string, optional) — New name.
- `description` (string, optional) — New description.
- `imageUrl` (string, optional) — New image URL.
- `linkUrl` (string, optional) — New link URL.
- `mapsUrl` (string, optional) — New Google Maps URL.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/leaderboards/contenders/update" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "CONTENDER_ID",
    "description": "Brown sugar milk tea"
  }'
```

Response shape:

```ts
{
  data: {
    id: string;
    leaderboardId: string;
    name: string;
    description: string | null;
    imageUrl: string | null;
    linkUrl: string | null;
    mapsUrl: string | null;
    score: number; // the Elo rating, the net vote, or the average stars
    eloRating: number;
    matchupVotes: number;
    ratingSum: number;
    ratingCount: number;
    addedByProfileId: number;
    createdAt: number;
  };
}
```

### Delete contender

`POST /api/v1/leaderboards/contenders/delete`

Delete a contender with its votes and ratings. The person who added it or the board creator. Requires leaderboards:write.

Parameters:

- `id` (string, required) — Contender id.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/leaderboards/contenders/delete" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "CONTENDER_ID"
  }'
```

Response shape:

```ts
{
  data: { id: string };
  deleted: true;
}
```

### Cast face-off vote

`POST /api/v1/leaderboards/votes/cast`

Record a face-off vote: the winner beats the loser, and both Elo ratings move. Both contenders must be on the same face-off board. Requires leaderboards:write.

Parameters:

- `winnerId` (string, required) — The contender that wins.
- `loserId` (string, required) — The contender that loses.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/leaderboards/votes/cast" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "winnerId": "CONTENDER_ID",
    "loserId": "OTHER_CONTENDER_ID"
  }'
```

Response shape:

```ts
{
  data: {
    winner: {
      id: string;
      leaderboardId: string;
      name: string;
      description: string | null;
      imageUrl: string | null;
      linkUrl: string | null;
      mapsUrl: string | null;
      score: number; // the Elo rating, the net vote, or the average stars
      eloRating: number;
      matchupVotes: number;
      ratingSum: number;
      ratingCount: number;
      addedByProfileId: number;
      createdAt: number;
    };
    loser: {
      id: string;
      leaderboardId: string;
      name: string;
      description: string | null;
      imageUrl: string | null;
      linkUrl: string | null;
      mapsUrl: string | null;
      score: number; // the Elo rating, the net vote, or the average stars
      eloRating: number;
      matchupVotes: number;
      ratingSum: number;
      ratingCount: number;
      addedByProfileId: number;
      createdAt: number;
    };
  };
}
```

### Set rating

`POST /api/v1/leaderboards/ratings/set`

Set the token owner's vote on an up/down board (1 or -1) or rating on a star board (1 to 5), with a review on a Review board. A second call changes the owner's rating; it does not add one. Requires leaderboards:write.

Parameters:

- `contenderId` (string, required) — Contender id.
- `value` (number, required) — 1 or -1 on an up/down board, 1 to 5 on a star board.
- `review` (string, optional) — Review boards only: the review text.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/leaderboards/ratings/set" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "contenderId": "CONTENDER_ID",
    "value": 5
  }'
```

Response shape:

```ts
{
  data: {
    id: string;
    leaderboardId: string;
    name: string;
    description: string | null;
    imageUrl: string | null;
    linkUrl: string | null;
    mapsUrl: string | null;
    score: number; // the Elo rating, the net vote, or the average stars
    eloRating: number;
    matchupVotes: number;
    ratingSum: number;
    ratingCount: number;
    addedByProfileId: number;
    createdAt: number;
  };
}
```
