# GraphQL Reference

> Query and mutate Done Bear resources over the GraphQL endpoint, including tasks, projects, teams, labels, workspaces, and API keys.

The Done Bear GraphQL endpoint handles both reads and writes for tasks, projects, teams, labels, workspaces, and API keys.

## Endpoint

Send `POST` requests to:

```bash
curl https://api.donebear.com/graphql \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  --data '{"query":"query { viewer { id email } }"}'
```

## Common queries

### `viewer`

Returns the currently authenticated user.

```bash
curl https://api.donebear.com/graphql \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  --data '{
    "query": "query { viewer { id email name username } }"
  }'
```

### `tasks`

Lists tasks with Relay-style pagination and filtering.

```graphql
query Tasks($workspaceId: ID!) {
  tasks(first: 20, filter: { workspaceId: { eq: $workspaceId } }) {
    nodes {
      id
      title
      description
      start
      startBucket
      deadlineAt
      completedAt
      archivedAt
      projectId
      teamId
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

```bash
curl https://api.donebear.com/graphql \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  --data '{
    "query": "query Tasks($workspaceId: ID!) { tasks(first: 20, filter: { workspaceId: { eq: $workspaceId } }) { nodes { id title description start startBucket deadlineAt completedAt archivedAt projectId teamId } pageInfo { hasNextPage endCursor } } }",
    "variables": { "workspaceId": "<workspace-id>" }
  }'
```

There is no `view` argument. A task's view is derived from its state and start
fields rather than stored as a column, so the GraphQL API returns the fields and
you derive the bucket. The two state-derived views are a filter away:

```graphql
# Logbook: completed and not archived, newest first.
tasks(
  first: 20
  filter: {
    workspaceId: { eq: $workspaceId }
    archivedAt: { null: true }
    completedAt: { null: false }
  }
  orderBy: { completedAt: DESC }
) {
  nodes {
    id
    title
    completedAt
  }
}
```

Archived wins over completed, so Trash is `archivedAt: { null: false }` and the
open buckets are `archivedAt: { null: true }, completedAt: { null: true }`.
`completedAt` is an instant, not a date-only field: convert it to the reader's
time zone before grouping by day. The CLI (`donebear task list --view logbook`)
and the MCP server (`task_list` with `view: "logbook"`) do this derivation for
you.

### `projects`

Lists projects in one workspace.

```graphql
query Projects($workspaceId: ID!) {
  projects(first: 20, filter: { workspaceId: { eq: $workspaceId } }) {
    nodes {
      id
      key
      name
      status
      targetDate
      archivedAt
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

```bash
curl https://api.donebear.com/graphql \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  --data '{
    "query": "query Projects($workspaceId: ID!) { projects(first: 20, filter: { workspaceId: { eq: $workspaceId } }) { nodes { id key name status targetDate archivedAt } pageInfo { hasNextPage endCursor } } }",
    "variables": { "workspaceId": "<workspace-id>" }
  }'
```

## Workspace operations

### `myWorkspaces` query

Lists the workspaces available to the authenticated user, including the user's role in each.

```bash
curl https://api.donebear.com/graphql \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  --data '{
    "query": "query { myWorkspaces { id name urlKey role } }"
  }'
```

### `workspaceCreate` mutation

Creates a new workspace.

```graphql
mutation CreateWorkspace($input: WorkspaceCreateInput!) {
  workspaceCreate(input: $input) {
    workspace {
      id
      name
      urlKey
    }
  }
}
```

```bash
curl https://api.donebear.com/graphql \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  --data '{
    "query": "mutation CreateWorkspace($input: WorkspaceCreateInput!) { workspaceCreate(input: $input) { workspace { id name urlKey } } }",
    "variables": { "input": { "name": "My Team", "urlKey": "my-team" } }
  }'
```

### `workspaceJoin` mutation

Joins a workspace using an invitation code. Email invites are single-use. Share
links (`kind: "share"`) can be opened more than once. When the code is a
project-scoped share link, `projectKey` is the project to open after join.

```graphql
mutation JoinWorkspace($code: String!) {
  workspaceJoin(code: $code) {
    workspace {
      id
      name
      urlKey
    }
    projectKey
  }
}
```

```bash
curl https://api.donebear.com/graphql \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  --data '{
    "query": "mutation JoinWorkspace($code: String!) { workspaceJoin(code: $code) { workspace { id name urlKey } projectKey } }",
    "variables": { "code": "ABCD2345" }
  }'
```

`workspaceInvitationCreate` accepts `kind: "share"` (and optional `projectId`)
to mint or return the reusable join link. Omit `email` for share links.

### `workspaceHistory` query

Returns recent activity for a workspace, optionally filtered by model.

```graphql
query WorkspaceHistory($workspaceId: ID!, $limit: Int, $model: String) {
  workspaceHistory(workspaceId: $workspaceId, limit: $limit, model: $model) {
    id
    action
    model
    createdAt
  }
}
```

```bash
curl https://api.donebear.com/graphql \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  --data '{
    "query": "query WorkspaceHistory($workspaceId: ID!, $limit: Int, $model: String) { workspaceHistory(workspaceId: $workspaceId, limit: $limit, model: $model) { id action model createdAt } }",
    "variables": { "workspaceId": "<workspace-id>", "limit": 50 }
  }'
```

## API key operations

### `apiKeys` query

Lists API keys for the authenticated user. Raw tokens are not returned.

```bash
curl https://api.donebear.com/graphql \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  --data '{
    "query": "query { apiKeys { id label keyPrefix createdAt } }"
  }'
```

### `apiKeyCreate` mutation

Creates a new API key. The raw token is returned only at creation time.

```graphql
mutation CreateApiKey($label: String!) {
  apiKeyCreate(label: $label) {
    key {
      id
      label
      keyPrefix
      token
      createdAt
    }
  }
}
```

```bash
curl https://api.donebear.com/graphql \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  --data '{
    "query": "mutation CreateApiKey($label: String!) { apiKeyCreate(label: $label) { key { id label keyPrefix token createdAt } } }",
    "variables": { "label": "CI" }
  }'
```

### `apiKeyRevoke` mutation

Revokes an API key.

```graphql
mutation RevokeApiKey($id: ID!) {
  apiKeyRevoke(id: $id) {
    success
  }
}
```

```bash
curl https://api.donebear.com/graphql \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  --data '{
    "query": "mutation RevokeApiKey($id: ID!) { apiKeyRevoke(id: $id) { success } }",
    "variables": { "id": "<key-id>" }
  }'
```

## Top-level queries reference

The API exposes these top-level queries:

- `viewer`
- `taskByNumber`, `tasks`
- `projects`
- `labels`
- `teams`
- `workspaces`
- `taskChecklistItem`, `taskChecklistItems`
- `myWorkspaces`
- `apiKeys`
- `workspaceHistory`

## Response shape

The endpoint returns standard GraphQL responses:

```json
{
  "data": {
    "viewer": {
      "id": "6fd41760-f5ee-4f60-80a6-8d15aeb11043",
      "email": "user@example.com"
    }
  },
  "errors": []
}
```

Invalid queries return GraphQL-style errors:

```json
{
  "data": null,
  "errors": [
    {
      "message": "Unknown query",
      "extensions": {
        "code": "INTERNAL_ERROR"
      }
    }
  ]
}
```

Domain failures from a mutation also appear as typed `UserError` objects on the payload:

```graphql
mutation WorkspaceCreate($input: WorkspaceCreateInput!) {
  workspaceCreate(input: $input) {
    success
    errors {
      code
      message
      field
    }
    workspace {
      id
    }
  }
}
```

Handle `errors[].code` (`WORKSPACE_NOT_FOUND`, `ACCESS_DENIED`, …) rather than parsing `message`. The same codes appear on top-level `errors[].extensions.code` when a resolver throws.

`Task.notes` is `@deprecated`; use `Task.description`. See [API versioning](/api/versioning).

## Rate limits

The `Query` and `Mutation` types declare `@rateLimit(limit: 100, durationSeconds: 60)`. That is the API origin's per-IP ceiling. Query depth is capped at 10.

## Guidance

- Always send a bearer token even when a query appears to work without one.
- Scope your reads by workspace whenever the field supports it.
- Treat undocumented schema details as unstable. Documented fields follow the [versioning policy](/api/versioning).

## Next steps

- Follow [Authenticate API requests](/api/authentication) for credential setup.

---

Every Done Bear page is listed at https://donebear.com/llms.txt. Any page has a markdown copy at its URL plus `.md`. Start from the map rather than guessing a path.
