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

# Overview

> Collaborative whiteboard boards from Miro - create and manage boards, add sticky notes, shapes, cards, text, frames, embeds and connectors, and organise a board with tags and members.

Use **Miro** through Corsair: one client, typed API calls.

Send a Miro access token as a bearer token. Create an app in Miro under Settings, Your apps, install it on your team and copy the access token it gives you.

Leave **Expire user authorization token** unticked when you create the app. An expiring token lasts 60 minutes, and this plugin stores the token as an API key and does not refresh it, so calls start failing with 401 once it lapses. Miro does not let you change this after the app is created.

Things that shape the calls:

* **Boards page by offset, items by cursor.** `boards.list` takes `limit` and `offset` and reports `total`; `items.list` and `connectors.list` take a `cursor` and return the next one. Members, tags and `tags.items` page by offset. `tags.ofItem` returns every tag on the item in one response.
* **List rows arrive as `data`.** Miro names every list array `data` whatever the resource, so each operation renames it after the resource: `boards`, `items`, `tags`, `members`, `connectors`. `tags.ofItem` is the exception and keys them `tags`.
* **Item writes are nested.** Miro expects a sticky note's `content` and `shape` under `data`, its colour under `style`, and its frame under `parent.id`. This plugin takes them flat and groups them for you.
* **Connectors need two existing items.** Both `startItemId` and `endItemId` are required and must already be on the board; a connector cannot be created against a loose point.
* **Tags on items travel in the query.** `tags.attach` and `tags.removeFromItem` both address `/v2/boards/{boardId}/items/{itemId}` with `tag_id` as a query parameter. Each call handles one tag, so attach several by calling it once per tag; an earlier tag stays attached.
* **`boards.copy` is a PUT on the collection** with `copy_from` as a required query parameter, not a body field.
* **Scopes.** Reads need `boards:read`, writes need `boards:write`. A token without the write scope gets 403.
* **Rate limits** are per minute by tier, and a 429 carries `Retry-After`; retries are capped at three.
* **A 5xx is retried on reads only.** A write that answers 5xx may still have been applied, so it is not sent again; check the board before repeating it.

**What you get:**

* 40 typed API operations

## Setup

**1. Install**

```bash npm theme={null}
npm install corsair @corsair-dev/miro better-sqlite3
npm install --save-dev @types/better-sqlite3
```

```bash yarn theme={null}
yarn add corsair @corsair-dev/miro better-sqlite3
yarn add --dev @types/better-sqlite3
```

```bash pnpm theme={null}
pnpm install corsair @corsair-dev/miro better-sqlite3
pnpm install --save-dev @types/better-sqlite3
```

```bash bun theme={null}
bun add corsair @corsair-dev/miro better-sqlite3
bun add --dev @types/better-sqlite3
```

**2. Add the plugin**

```ts corsair.ts theme={null}
import Database from 'better-sqlite3';
import { createCorsair } from 'corsair';
import { miro } from '@corsair-dev/miro';

export const corsair = createCorsair({
	plugins: [
		miro(),
	],
	database: new Database('corsair.db'),
	kek: process.env.CORSAIR_KEK!,
	multiTenancy: true,
	hub: {
		projectApiKey: process.env.CORSAIR_API_KEY!,
		signingSecret: process.env.CORSAIR_SIGNING_SECRET!,
	},
});
```

With `multiTenancy: true`, scope calls with `corsair.withTenant(id)`. See [Quick start](https://docs.corsair.dev/quick-start.md) for KEK + Hub keys, and [Multi-tenancy](https://docs.corsair.dev/concepts/multi-tenancy.md) for account isolation.

**3. Choose authentication**

> [tab] API Key (Recommended)
>
> No setup required yet. When you make your first request as a tenant, Corsair prompts for the API key.
>
> ```ts theme={null}
> miro()
> ```
>
> More: [API Key](https://docs.corsair.dev/concepts/api-key)

**4. Connect a tenant**

Mint a connect link and send the tenant to it. Hub hosts the page and delivers the result to your app — see [Connect / OAuth](https://docs.corsair.dev/management/connect).

```ts theme={null}
const { connectUrl } = await corsair.manage.connect.createLink({
	plugin: 'miro',
	tenantId: 'acme',
});
// redirect the user's browser to connectUrl
```

## Example API calls

**List the boards you can see**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.miro.api.boards.list({ limit: 10 });
```

**Add a sticky note to a board**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.miro.api.stickyNotes.create({ boardId: 'uXjVK1abcd=', content: 'Ship the roadmap' });
```

See the full list on the [API](https://docs.corsair.dev/plugins/miro/api.md) page.

## What's next

> [card] [API reference](https://docs.corsair.dev/plugins/miro/api.md)
>
> Every `miro.api.*` operation with input and output types.

> [card] [Connect / OAuth](https://docs.corsair.dev/management/connect)
>
> createLink, Hub delivery, and tenant connect flows.

> [card] [Use with agents](https://docs.corsair.dev/mcp-adapters/mcp-adapters)
>
> Expose this plugin's operations as MCP tools.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.