## Authentication

All API endpoints require session authentication. You must be logged in via the web interface, or send the session cookie with your requests.

For programmatic access, authenticate first:

```bash
# Login and save cookies
curl -c cookies.txt -X POST https://your-yando-instance/accounts/login/ \
  -d "login=user@example.com&password=yourpassword" \
  -H "Referer: https://your-yando-instance/"
```

Then include `-b cookies.txt` in subsequent requests.

## Rate Limiting

API endpoints are rate-limited per user. If you exceed the rate limit, you'll receive a `429 Too Many Requests` response.

| Scope | Limit |
|-------|-------|
| General API requests | 100 / hour |
| Board create | 30 / hour |
| Board delete | 10 / hour |
| Board import | 20 / hour |
| Project create | 20 / hour |
| Project delete | 10 / hour |

The specific limits are separate allowances — creating a board does not use up your general 100/hour.

**Keeping well under the limit.** Adding objects counts against the general allowance, so creating them one request at a time is what usually exhausts it. Use the bulk object creation endpoint below (`POST /api/boards/{board_id}/objects/bulk/`) to add many objects in a single request, and target zones by **name** rather than fetching the board to look up a zone ID. Together these take a typical "create a board and fill it with 16 notes" workflow from around 34 requests down to 3.

Limits reset on a rolling one-hour window. If your account has a legitimate need for higher throughput — a demo environment or an internal integration — an administrator can exempt it from rate limits.

---

## POST /api/boards/import/

Create a new board from exported JSON data.

### Request

Accepts two content types:

**JSON body** (`application/json`):

```bash
curl -b cookies.txt -X POST https://your-yando-instance/api/boards/import/ \
  -H "Content-Type: application/json" \
  -H "X-CSRFToken: <token>" \
  -d @board-export.json
```

**File upload** (`multipart/form-data`):

```bash
curl -b cookies.txt -X POST https://your-yando-instance/api/boards/import/ \
  -H "X-CSRFToken: <token>" \
  -F "json_file=@board-export.json"
```

### Query Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `template` | string | Template name or UUID to override the JSON template |
| `title` | string | Override the board title from the JSON |
| `description` | string | Override the board description |

### Response (201 Created)

```json
{
  "id": "a1b2c3d4-...",
  "title": "My Board",
  "description": "Board description",
  "template": "Business Model Canvas",
  "object_count": 15,
  "url": "/boards/a1b2c3d4-.../"
}
```

### Errors

| Status | Description |
|--------|-------------|
| 400 | Invalid JSON, validation error, or missing required fields |
| 403 | Authentication required |

---

## POST /api/boards/{board_id}/objects/bulk/

Create many objects on a board in a single request.

Use this instead of calling `POST /api/boards/{board_id}/objects/` in a loop. Each request counts against your rate limit, so filling a 16-note board costs 1 request this way instead of 16.

### Request

```bash
curl -b cookies.txt -X POST \
  https://your-yando-instance/api/boards/{board_id}/objects/bulk/ \
  -H "Content-Type: application/json" \
  -d '{
    "objects": [
      {
        "object_type": "sticky_note",
        "zone": "Strengths",
        "data": {"text": "Strong brand recognition", "color": "#7BC950"}
      },
      {
        "object_type": "sticky_note",
        "zone": "Weaknesses",
        "data": {"text": "Thin margins", "color": "#FF6B6B"}
      }
    ]
  }'
```

### Object Fields

| Field | Required | Description |
|-------|----------|-------------|
| `object_type` | Yes | `sticky_note`, `text_box`, `shape`, `line`, or `image` |
| `data` | Yes | Type-specific properties — `text` and `color` for a sticky note |
| `zone` | No | Zone **name** (case-insensitive) or UUID on the board's template |
| `zone_id` | No | Zone UUID. Takes precedence over `zone` |
| `x`, `y` | No | Position. Derived from the zone when omitted |
| `width`, `height` | No | Size. Defaults to 200×150 for sticky notes, 300×100 for text boxes |
| `rotation` | No | Degrees (default 0) |
| `opacity` | No | 0.0–1.0 (default 1.0) |

**Targeting zones by name.** Passing `zone: "Strengths"` saves you fetching the board first to find its UUID. An unknown name returns a 400 listing the zones that do exist.

**Automatic placement.** When you give a zone and omit `x`/`y`, the server positions each object inside that zone, spacing successive objects so they don't overlap. Supply explicit coordinates to override this.

**Limits.** Between 1 and 200 objects per request. Send larger imports in chunks.

### Response (201 Created)

```json
{
  "count": 2,
  "created": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "object_type": "sticky_note",
      "x": 40.0,
      "y": 30.0,
      "width": 200.0,
      "height": 150.0,
      "data": {"text": "Strong brand recognition", "color": "#7BC950"},
      "zone_name": "Strengths",
      "version": 1
    }
  ]
}
```

Objects are returned in the order you sent them. Collaborators with the board open see them appear live.

### Errors

The request is **atomic** — if any object is invalid, none are created:

| Status | Description |
|--------|-------------|
| 400 | Empty list, more than 200 objects, unknown zone name, or an invalid entry |
| 403 | You do not have editor access to this board |
| 404 | Board not found |

Validation errors name the offending entry, for example:

```json
{"error": "Object at index 1: Zone 'Sxrengths' does not exist on this board's template. Available zones: Strengths, Weaknesses, Opportunities, Threats"}
```

---

## POST /api/boards/{board_id}/resize/

Resize a board's canvas dimensions.

### Request

```bash
curl -b cookies.txt -X POST \
  https://your-yando-instance/api/boards/{board_id}/resize/ \
  -H "Content-Type: application/json" \
  -H "X-CSRFToken: <token>" \
  -d '{"width": 4000, "height": 2400}'
```

### Response (200 OK)

```json
{
  "id": "a1b2c3d4-...",
  "canvas_width": 4000,
  "canvas_height": 2400
}
```

---

## GET /api/boards/{board_id}/snapshots/

List all snapshots for a board, ordered by creation date (newest first).

### Request

```bash
curl -b cookies.txt \
  https://your-yando-instance/api/boards/{board_id}/snapshots/
```

### Response (200 OK)

```json
[
  {
    "id": "s1n2a3p4-...",
    "created_at": "2026-02-21T08:00:00Z",
    "object_count": 24,
    "board_version": 15,
    "change_summary": {
      "added": {"sticky_note": 2},
      "edited": {"text_box": 1},
      "deleted": {}
    }
  }
]
```

---

## GET /api/boards/{board_id}/snapshots/{snapshot_id}/

Retrieve a single snapshot's full data.

### Request

```bash
curl -b cookies.txt \
  https://your-yando-instance/api/boards/{board_id}/snapshots/{snapshot_id}/
```

### Response (200 OK)

Returns the full snapshot including `snapshot_data` containing the complete board state at that point in time.

---

## POST /api/projects/{project_id}/pages/import/

Import markdown files as project pages.

### Request

Upload one or more `.md` or `.markdown` files:

```bash
curl -b cookies.txt -X POST \
  https://your-yando-instance/api/projects/{project_id}/pages/import/ \
  -H "X-CSRFToken: <token>" \
  -F "files=@notes.md" \
  -F "files=@planning.md"
```

### Response (201 Created)

```json
{
  "pages_created": 2,
  "pages": [
    {
      "id": "p1a2g3e4-...",
      "title": "Notes",
      "url": "/projects/.../pages/p1a2g3e4-.../"
    }
  ],
  "errors": []
}
```

### Status Codes

| Status | Description |
|--------|-------------|
| 201 | All files imported successfully |
| 207 | Partial success - some files imported, some had errors |
| 400 | No files provided or all files failed |
| 403 | Not an owner or editor of the project |
| 404 | Project not found |

---

## JSON Export Schema Reference

When exporting a board to JSON, the structure is:

```json
{
  "board": {
    "title": "Board Title",
    "description": "Optional description",
    "template": "Template Name or null",
    "canvas_width": 3200,
    "canvas_height": 1800
  },
  "zones": {
    "Zone Name": [
      {
        "type": "sticky_note",
        "x": 100,
        "y": 200,
        "width": 200,
        "height": 150,
        "rotation": 0,
        "data": {
          "text": "Content here",
          "color": "#FFEB3B"
        }
      }
    ]
  },
  "unzoned_objects": [
    {
      "type": "text_box",
      "x": 500,
      "y": 100,
      "width": 300,
      "height": 50,
      "data": {
        "text": "Unzoned text"
      }
    }
  ]
}
```

### Object Types

| Type | Data Fields |
|------|-------------|
| `sticky_note` | `text`, `color` |
| `text_box` | `text`, `fontSize`, `fontWeight` |
| `shape` | `shapeType` (`rectangle`, `circle`, `triangle`, `line`), `fill`, `stroke` |
