> ## Documentation Index
> Fetch the complete documentation index at: https://docs.opensource-together.com/llms.txt
> Use this file to discover all available pages before exploring further.

# API Reference

> How the OpenSource Together API works: base URLs, response envelopes, errors, and authentication

## Overview

The OpenSource Together API is a REST API that powers projects, users, tech stacks and
categories on [opensource-together.com](https://opensource-together.com). Every endpoint on the
following pages is generated from the live OpenAPI specification, so what you read here is what
the API actually serves.

<Info>
  **The API is a closed-source service.** You don't need access to it to contribute. The
  web app ships a complete local mock of this exact contract. See
  [Run the app locally](/quickstart) and the [Mock API guide](/web-app/mock-api).
</Info>

## Base URL

<CodeGroup>
  ```bash Production theme={null}
  https://api.opensource-together.com
  ```

  ```bash Local (mock API) theme={null}
  http://localhost:4000
  ```
</CodeGroup>

Paths are mounted at the root. It's `/users/me`, not `/api/users/me`. The interactive
playground on each endpoint page targets production.

The API's own Swagger UI is available at
[api.opensource-together.com/api-docs](https://api.opensource-together.com/api-docs), and the
raw specification at `/api-docs-json`.

## Authentication

Authentication is **cookie-based**, handled by [better-auth](https://better-auth.com) mounted
at `/api/auth/*`. There are no API keys and no `Authorization` header.

Every request must send cookies:

```typescript theme={null}
const response = await fetch("https://api.opensource-together.com/users/me", {
  credentials: "include",
});
```

The session cookie is named `better-auth.session_token`. Requests without a valid session
against a protected endpoint return `401`.

<Warning>
  The `/api/auth/*` routes are **not part of the OpenAPI specification below**. Better-auth
  mounts its own handler, which the generator doesn't see. They're documented here instead.
</Warning>

### Auth routes used by the app

| Route                           | Purpose                                                               |
| ------------------------------- | --------------------------------------------------------------------- |
| `POST /api/auth/sign-in/social` | Start OAuth sign-in, then redirect to `/api/auth/callback/{provider}` |
| `GET /api/auth/get-session`     | Read the current session                                              |
| `POST /api/auth/sign-out`       | End the session                                                       |
| `POST /api/auth/link-social`    | Link an additional provider to the account                            |
| `POST /api/auth/unlink-account` | Unlink a provider                                                     |
| `POST /api/auth/delete-user`    | Delete the account                                                    |

Sign-in providers exposed by the app are **GitHub** and **GitLab**. Email/password sign-in is
disabled.

<Note>
  In practice the web app treats `GET /users/me` as its source of truth for "who am I". It
  returns the full profile, and `null` on a `401`.
</Note>

## Response format

### Success

Every successful response wraps its payload in `data` and carries a server `timestamp`:

```json theme={null}
{
  "data": {
    "id": "usr_4f1a9c2e8b7d4e3fa6c5b8d9e0f1a2b3",
    "username": "octocat"
  },
  "timestamp": "2026-08-18T19:15:31.008Z"
}
```

### Paginated collections

List endpoints add a `pagination` block alongside `data`:

```json theme={null}
{
  "data": [{ "id": "pjt_7be5a3372ecc48498b1d6a06bc13c34b", "title": "dsh-desktop" }],
  "pagination": {
    "total": 3121,
    "lastPage": 3121,
    "currentPage": 1,
    "size": 1
  },
  "timestamp": "2026-08-18T19:18:59.924Z"
}
```

Paginated endpoints accept `page` (from `1`) and `per_page` (`1`–`100`).

### Errors

`error` is a **string**, not an object. `details` is optional and carries a machine-readable
code:

```json theme={null}
{
  "error": "Unauthorized",
  "statusCode": 401,
  "timestamp": "2026-08-18T19:15:31.008Z",
  "details": {
    "code": "UNAUTHORIZED",
    "message": "Unauthorized"
  }
}
```

Validation failures return a list of field errors instead:

```json theme={null}
{
  "errors": [{ "field": "title", "message": "Title is required" }],
  "statusCode": 400,
  "timestamp": "2026-08-18T19:15:31.008Z"
}
```

Some write endpoints return `204 No Content` with an empty body, such as deleting a project and
removing a bookmark.

## Resource IDs

Public identifiers are prefixed strings, so an ID is always self-describing:

| Prefix | Resource   | Example                                |
| ------ | ---------- | -------------------------------------- |
| `usr_` | User       | `usr_4f1a9c2e8b7d4e3fa6c5b8d9e0f1a2b3` |
| `pjt_` | Project    | `pjt_7be5a3372ecc48498b1d6a06bc13c34b` |
| `tst_` | Tech stack | `tst_34c5c442c32d45c9b3d8688c613fa914` |
| `cat_` | Category   | `cat_937d7c10f960478383a7e3e556a65016` |

The `/users/{id}` routes also accept the literal `me` in place of an ID.

## Endpoints

<CardGroup cols={2}>
  <Card title="Projects" icon="folder" href="/api-reference/endpoint/project/list-projects-with-filters-and-pagination">
    Discovery, creation, issues, bookmarks, claiming, and media
  </Card>

  <Card title="Users" icon="users" href="/api-reference/endpoint/user/get-current-authenticated-user">
    Profiles, repositories, pull requests, and bookmarks
  </Card>

  <Card title="Tech Stacks" icon="code" href="/api-reference/endpoint/techstacks/get-all-tech-stacks">
    The technology and language taxonomy
  </Card>

  <Card title="Categories" icon="tags" href="/api-reference/endpoint/categories/get-all-categories">
    The project category taxonomy
  </Card>

  <Card title="Health" icon="heart-pulse" href="/api-reference/endpoint/health/check-apps-health">
    Service health check
  </Card>
</CardGroup>

## Keeping this reference up to date

The specification is a direct dump of the API's Swagger output, never hand-edited. To refresh
it, see [Contribute to the docs](/development).
