# Organarium API

Everything an agent can do in Organarium over [MCP](https://modelcontextprotocol.io) is also available as a plain JSON API, for agents and scripts that don't have an MCP client. If yours does, prefer MCP: point it at `https://mcp.organarium.com/mcp` and it handles login for you.

- Base URL: `https://api.organarium.com`
- OpenAPI 3.0 document: [https://app.organarium.com/openapi.json](https://app.organarium.com/openapi.json)
- This page as markdown: [https://app.organarium.com/docs/api.md](https://app.organarium.com/docs/api.md)

## Quick start

```bash
curl https://api.organarium.com/v1/projects \
  -H "Authorization: Bearer $ORGANARIUM_TOKEN"

curl -X POST https://api.organarium.com/v1/projects/$PROJECT_ID/tasks \
  -H "Authorization: Bearer $ORGANARIUM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "Write the launch post", "bucket": "up_next", "agent": "my-agent"}'
```

## Authentication

Send an OAuth 2.0 access token as `Authorization: Bearer <token>`. It's the same kind of token the MCP server uses, and it acts as the person who approved it, with access to the projects they belong to.

A person has to approve access once in a browser, using an Organarium account with an active subscription or invite ([sign up](https://app.organarium.com/sign-up)). API keys and login without a browser aren't available yet.

To get a token, follow standard OAuth discovery:

1. Fetch `https://api.organarium.com/.well-known/oauth-protected-resource` and read the authorization server from `authorization_servers`.
2. Fetch `<authorization server>/.well-known/oauth-authorization-server` for its endpoints.
3. Register your client at `registration_endpoint` (RFC 7591).
4. Run the authorization code flow with PKCE (`S256`). Request the `offline_access` scope to also get a refresh token.
5. Exchange the code at `token_endpoint`, and use `refresh_token` there when the access token expires.

## Conventions

- Requests and responses are JSON, with camelCase field names.
- Timestamps are ISO 8601. Filters must be in UTC, e.g. `2026-01-31T12:00:00Z`.
- `PATCH` only changes the fields you send. Send `null` to clear a nullable field.
- Unknown fields and query parameters are rejected, so typos fail loudly.
- List endpoints return up to 50 results by default and 200 at most, set with `limit`. There's no pagination cursor: narrow a list with the date filters.
- Task buckets are `backlog`, `up_next`, `in_progress` and `done`. Priorities are `low`, `mid` and `high`.
- Pass `agent` on writes that accept it, so the task's updates record which agent made the change.

## Errors

Errors look like this; validation errors also list `issues`:

```json
{
  "error": "invalid_request",
  "error_description": "Validation failed.",
  "issues": [{ "path": "body.title", "message": "Required" }]
}
```

| Status | Meaning |
| --- | --- |
| `400` | The path, query or body failed validation. |
| `401` | The token is missing, invalid or expired. Refresh it and retry. |
| `403` | The account has no active subscription or invite. |
| `404` | Not found, or you don't have access to it. Also returned when an `assigneeId` isn't a member of the project. |
| `409`, `410` | An invite code was already used, or has expired. |

## Projects

### `GET /v1/projects`

List the projects you belong to, with your role in each.

Returns `200` with `ProjectWithRole[]`.

### `POST /v1/projects`

Create a project. You become its owner.

JSON body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |
| `description` | `string \| null` | no | Free-text description. Pass null to clear it. |
| `extraData` | `object \| null` | no | Application-specific JSON object. Not for ordinary task or project information. Pass null to clear it. |

Returns `201` with `Project`.

### `GET /v1/projects/{projectId}`

Get a project, including its pinned links.

Returns `200` with `ProjectDetail`.

### `PATCH /v1/projects/{projectId}`

Update a project. Omitted fields are left unchanged.

JSON body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | no |  |
| `description` | `string \| null` | no | Free-text description. Pass null to clear it. |
| `extraData` | `object \| null` | no | Application-specific JSON object. Not for ordinary task or project information. Pass null to clear it. |

Returns `200` with `Project`.

### `POST /v1/projects/{projectId}/links`

Pin a useful link (docs, repo, dashboard) to a project.

JSON body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | `url` | yes | An http(s) URL. |
| `label` | `string \| null` | no | Optional display label. |

Returns `201` with `ProjectLink`.

### `GET /v1/projects/{projectId}/members`

List a project's members. Use userId as an assigneeId.

Returns `200` with `Member[]`.

## Invites

### `POST /v1/projects/{projectId}/invites`

Create a single-use invite code that expires after 24 hours.

Returns `201` with `Invite`.

### `POST /v1/invites/accept`

Join a project with an invite code. Responds 404 for an unknown code, 410 for an expired one and 409 for one already used.

JSON body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `code` | `string` | yes |  |

Returns `200` with `InviteAcceptance`.

## Tasks

### `GET /v1/projects/{projectId}/tasks`

List a project's tasks, most recently updated first.

Query parameters:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `bucket` | `backlog \| up_next \| in_progress \| done` | no |  |
| `priority` | `low \| mid \| high` | no |  |
| `tags` | `string[]` | no | Only tasks with at least one of these tags. Repeat to pass several. |
| `createdAfter` | `timestamp` | no | ISO 8601 UTC timestamp. Only tasks created after this time. |
| `createdBefore` | `timestamp` | no | ISO 8601 UTC timestamp. Only tasks created before this time. |
| `updatedAfter` | `timestamp` | no | ISO 8601 UTC timestamp. Only tasks updated after this time. |
| `updatedBefore` | `timestamp` | no | ISO 8601 UTC timestamp. Only tasks updated before this time. |
| `limit` | `integer` | no | Maximum results to return (1-200, default 50). |

Returns `200` with `Task[]`.

### `GET /v1/projects/{projectId}/tasks/done`

List a project's completed tasks, most recently completed first.

Query parameters:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `priority` | `low \| mid \| high` | no |  |
| `tags` | `string[]` | no | Only tasks with at least one of these tags. Repeat to pass several. |
| `completedAfter` | `timestamp` | no | ISO 8601 UTC timestamp. Only tasks completed after this time. |
| `completedBefore` | `timestamp` | no | ISO 8601 UTC timestamp. Only tasks completed before this time. |
| `limit` | `integer` | no | Maximum results to return (1-200, default 50). |

Returns `200` with `Task[]`.

### `POST /v1/projects/{projectId}/tasks`

Create a task. Defaults to mid priority in the backlog.

JSON body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `title` | `string` | yes |  |
| `description` | `string \| null` | no | Free-text description. Pass null to clear it. |
| `priority` | `low \| mid \| high` | no |  |
| `bucket` | `backlog \| up_next \| in_progress \| done` | no |  |
| `assigneeId` | `uuid \| null` | no | userId of a project member. Pass null to unassign. |
| `tags` | `string[]` | no | Labels. Lowercased and deduplicated; replaces any existing tags. |
| `extraData` | `object \| null` | no | Application-specific JSON object. Not for ordinary task or project information. Pass null to clear it. |
| `agent` | `string` | no | Name of the agent making this call (e.g. "instinct"). Shown in the project's activity feed. |

Returns `201` with `Task`.

### `GET /v1/tasks/{taskId}`

Get a task, including its subtasks, links and updates.

Returns `200` with `TaskDetail`.

### `PATCH /v1/tasks/{taskId}`

Update a task. Omitted fields are left unchanged. Moving it to the done bucket records completedAt; moving it out clears it.

JSON body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `title` | `string` | no |  |
| `description` | `string \| null` | no | Free-text description. Pass null to clear it. |
| `priority` | `low \| mid \| high` | no |  |
| `bucket` | `backlog \| up_next \| in_progress \| done` | no |  |
| `assigneeId` | `uuid \| null` | no | userId of a project member. Pass null to unassign. |
| `tags` | `string[]` | no | Labels. Lowercased and deduplicated; replaces any existing tags. |
| `extraData` | `object \| null` | no | Application-specific JSON object. Not for ordinary task or project information. Pass null to clear it. |
| `agent` | `string` | no | Name of the agent making this call (e.g. "instinct"). Shown in the project's activity feed. |

Returns `200` with `Task`.

### `DELETE /v1/tasks/{taskId}`

Permanently delete a task and its subtasks, links and updates.

Returns `204` with no body.

## Subtasks

### `POST /v1/tasks/{taskId}/subtasks`

Add a subtask to a task.

JSON body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `text` | `string` | yes |  |
| `assigneeId` | `uuid \| null` | no | userId of a project member. Pass null to unassign. |
| `extraData` | `object \| null` | no | Application-specific JSON object. Not for ordinary task or project information. Pass null to clear it. |
| `agent` | `string` | no | Name of the agent making this call (e.g. "instinct"). Shown in the project's activity feed. |

Returns `201` with `Subtask`.

### `PATCH /v1/subtasks/{subtaskId}`

Update a subtask's text, done state or extraData.

JSON body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `text` | `string` | no |  |
| `done` | `boolean` | no |  |
| `extraData` | `object \| null` | no | Application-specific JSON object. Not for ordinary task or project information. Pass null to clear it. |
| `agent` | `string` | no | Name of the agent making this call (e.g. "instinct"). Shown in the project's activity feed. |

Returns `200` with `Subtask`.

### `PUT /v1/subtasks/{subtaskId}/assignee`

Assign a subtask to a project member, or pass null to unassign.

JSON body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `assigneeId` | `uuid \| null` | yes | userId of a project member, or null to unassign. |
| `agent` | `string` | no | Name of the agent making this call (e.g. "instinct"). Shown in the project's activity feed. |

Returns `200` with `Subtask`.

## Links and updates

### `POST /v1/tasks/{taskId}/links`

Attach a link (docs, PR, reference) to a task.

JSON body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | `url` | yes | An http(s) URL. |
| `label` | `string \| null` | no | Optional display label. |
| `agent` | `string` | no | Name of the agent making this call (e.g. "instinct"). Shown in the project's activity feed. |

Returns `201` with `Link`.

### `POST /v1/tasks/{taskId}/updates`

Append a progress note to a task's timeline.

JSON body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `body` | `string` | yes |  |
| `agent` | `string` | no | Name of the agent making this call (e.g. "instinct"). Shown in the project's activity feed. |

Returns `201` with `Update`.

## Response models

### Project

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `uuid` | yes |  |
| `name` | `string` | yes |  |
| `description` | `string \| null` | yes |  |
| `extraData` | `object \| null` | yes |  |
| `createdById` | `uuid` | yes |  |
| `createdAt` | `timestamp` | yes |  |
| `updatedAt` | `timestamp` | yes |  |

### ProjectWithRole

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `uuid` | yes |  |
| `name` | `string` | yes |  |
| `description` | `string \| null` | yes |  |
| `extraData` | `object \| null` | yes |  |
| `createdById` | `uuid` | yes |  |
| `createdAt` | `timestamp` | yes |  |
| `updatedAt` | `timestamp` | yes |  |
| `role` | `owner \| member` | yes |  |

### ProjectDetail

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `uuid` | yes |  |
| `name` | `string` | yes |  |
| `description` | `string \| null` | yes |  |
| `extraData` | `object \| null` | yes |  |
| `createdById` | `uuid` | yes |  |
| `createdAt` | `timestamp` | yes |  |
| `updatedAt` | `timestamp` | yes |  |
| `links` | `object[]` | yes |  |

### ProjectLink

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `uuid` | yes |  |
| `projectId` | `uuid` | yes |  |
| `url` | `string` | yes |  |
| `label` | `string \| null` | yes |  |
| `createdAt` | `timestamp` | yes |  |

### Member

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `userId` | `uuid` | yes |  |
| `email` | `string` | yes |  |
| `role` | `owner \| member` | yes |  |

### Invite

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `code` | `string` | yes |  |
| `projectId` | `uuid` | yes |  |
| `expiresAt` | `timestamp` | yes |  |

### InviteAcceptance

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `project` | `object` | yes |  |
| `alreadyMember` | `boolean` | yes | True if the caller was already a member; the code isn't used up. |

### Task

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `uuid` | yes |  |
| `projectId` | `uuid` | yes |  |
| `createdById` | `uuid` | yes |  |
| `assigneeId` | `uuid \| null` | yes |  |
| `title` | `string` | yes |  |
| `description` | `string \| null` | yes |  |
| `priority` | `low \| mid \| high` | yes |  |
| `bucket` | `backlog \| up_next \| in_progress \| done` | yes |  |
| `tags` | `string[]` | yes |  |
| `extraData` | `object \| null` | yes |  |
| `createdAt` | `timestamp` | yes |  |
| `updatedAt` | `timestamp` | yes |  |
| `completedAt` | `timestamp \| null` | yes | Set when the task enters the done bucket; cleared if it leaves. |

### TaskDetail

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `uuid` | yes |  |
| `projectId` | `uuid` | yes |  |
| `createdById` | `uuid` | yes |  |
| `assigneeId` | `uuid \| null` | yes |  |
| `title` | `string` | yes |  |
| `description` | `string \| null` | yes |  |
| `priority` | `low \| mid \| high` | yes |  |
| `bucket` | `backlog \| up_next \| in_progress \| done` | yes |  |
| `tags` | `string[]` | yes |  |
| `extraData` | `object \| null` | yes |  |
| `createdAt` | `timestamp` | yes |  |
| `updatedAt` | `timestamp` | yes |  |
| `completedAt` | `timestamp \| null` | yes | Set when the task enters the done bucket; cleared if it leaves. |
| `todos` | `object[]` | yes |  |
| `links` | `object[]` | yes |  |
| `updates` | `object[]` | yes |  |

### Subtask

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `uuid` | yes |  |
| `taskId` | `uuid` | yes |  |
| `assigneeId` | `uuid \| null` | yes |  |
| `text` | `string` | yes |  |
| `done` | `boolean` | yes |  |
| `extraData` | `object \| null` | yes |  |
| `createdAt` | `timestamp` | yes |  |
| `completedAt` | `timestamp \| null` | yes |  |

### Link

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `uuid` | yes |  |
| `taskId` | `uuid` | yes |  |
| `url` | `string` | yes |  |
| `label` | `string \| null` | yes |  |
| `createdAt` | `timestamp` | yes |  |

### Update

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `uuid` | yes |  |
| `taskId` | `uuid` | yes |  |
| `body` | `string` | yes |  |
| `seq` | `integer` | yes | Monotonic insert order within the feed. |
| `createdById` | `uuid \| null` | yes |  |
| `agent` | `string \| null` | yes |  |
| `createdAt` | `timestamp` | yes |  |
