# REST API

> Documentation index for AI agents: https://angelwood.io/docs/llms.txt

The Angelwood API lets you read clients, projects, tasks and invoices in your organization, and create clients and tasks. Nothing can be updated or deleted through the API.

- **Base URL:** `https://angelwood-api-uzil.onrender.com/api/v1`
- **OpenAPI description:** `https://angelwood-api-uzil.onrender.com/api/v1/openapi.json`
- **Format:** JSON requests and responses

## Authentication

Create an API key in Angelwood under **Settings**. Choose the permission the key needs:

| Permission | Allows |
| --- | --- |
| Read | Every `GET` endpoint |
| Write | Read, plus creating clients and tasks |
| Admin | The same as write in this API |

Send the key in the `Authorization` header on every request:

```bash
curl https://angelwood-api-uzil.onrender.com/api/v1/me \
  -H "Authorization: Bearer sk_live_your_key"
```

A key acts inside the one organization it was created in. It stops working when it is revoked, when it expires, or when the person who created it is no longer an active member of the organization. Members with the viewer or commenter role cannot create anything, whatever permission their key has.

## Lists and paging

List endpoints return the newest records first, in this shape:

```json
{
  "data": [],
  "total": 42,
  "has_more": true,
  "next_offset": 25
}
```

| Parameter | Meaning |
| --- | --- |
| `limit` | How many records to return, from 1 to 100. The default is 25. |
| `offset` | How many records to skip. Pass `next_offset` from the previous page. |
| `created_after` | Only records created after this ISO 8601 time, for example `2026-03-31T00:00:00Z`. |

To pick up new records on a schedule, call a list endpoint with `created_after` set to the time of your previous call.

## Endpoints

### GET /me

Tests the key. Returns the organization and user it belongs to, the user's role and the key's permissions.

### GET /clients

Lists active clients. Optional parameters: `search` (matches name or email), `created_after`, `limit`, `offset`.

### GET /clients/{client_id}

Returns one client.

### POST /clients

Creates a client. Needs write permission.

| Field | Required | Notes |
| --- | --- | --- |
| `name` | Yes | Up to 200 characters |
| `email` | No | Two active clients cannot share an email address |
| `phone` | No | |
| `address` | No | |

```bash
curl -X POST https://angelwood-api-uzil.onrender.com/api/v1/clients \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Ltd", "email": "ops@acme.example"}'
```

### GET /projects

Lists projects. Optional parameters: `client_id`, `status` (`active`, `completed`, `on_hold`, `cancelled`), `created_after`, `limit`, `offset`.

### GET /tasks

Lists tasks. Optional parameters: `project_id`, `status` (`todo`, `in_progress`, `completed`, `blocked`), `created_after`, `completed_after`, `limit`, `offset`.

With `status=completed` or `completed_after`, the most recently completed task comes first.

### POST /tasks

Adds a task to a project. Needs write permission.

| Field | Required | Notes |
| --- | --- | --- |
| `project_id` | Yes | A project in your organization |
| `title` | Yes | Up to 500 characters |
| `description` | No | |
| `priority` | No | `low`, `medium`, `high` or `critical`. The default is `medium`. |
| `due_date` | No | ISO 8601 time |

### GET /invoices

Lists invoices. Optional parameters: `client_id`, `project_id`, `status` (`draft`, `sent`, `paid`, `overdue`, `partially_paid`, `refunded`, `partially_refunded`, `cancelled`), `created_after`, `paid_after`, `limit`, `offset`.

Each invoice includes `amount`, `amount_paid` (the sum of completed payments), `balance_due` and `currency`. With `status=paid` or `paid_after`, the most recently paid invoice comes first.

## Errors

Errors use the HTTP status code and this body:

```json
{
  "error": {
    "code": "INVALID_API_KEY",
    "message": "Invalid, revoked or expired API key"
  }
}
```

| Status | Meaning |
| --- | --- |
| 400 | The request was not valid. `details` says which field. |
| 401 | The key is missing, invalid, revoked or expired. |
| 403 | The key or its owner is not allowed to do this. |
| 404 | The record does not exist in your organization. |
| 409 | The record conflicts with an existing one, such as a client with the same email. |
| 429 | Too many requests. |

## Rate limit

120 requests per minute per organization. Responses carry the standard `RateLimit` headers.
