> ## 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.

# Ragic plugin

> Read, filter, create, update and delete records on any Ragic sheet, post comments, lock records and read a sheet's field schema.

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

Generate an API key in Ragic under **Personal Settings**. It goes in `Authorization: Basic <key>` — the key is the Basic username and the password is empty, and the value is *not* base64-encoded, because Ragic reads it directly.

Four things trip up almost every integration:

* **Every call needs the sheet's coordinates.** A Ragic URL is `https://{server}/{account}{path}/{sheetIndex}`, so each operation takes `account`, `path` and `sheetIndex`, read straight off the URL you see in the browser. There is no way to address a sheet without them.
* **The server is per account.** `www.ragic.com` is the default; an account on another shard uses `na3`, `ap5`, `eu2` and so on, and a key only works on its own server. Pass `server: 'eu2.ragic.com'` when that is where your account lives.
* **A failure arrives as HTTP 200.** Ragic reports nearly every error inside the response body — `{"status":"ERROR","code":106,"msg":"…"}` — including an unrecognised key, a bad sheet index and a server fault. The plugin checks for that envelope and throws, so a refusal can never be mistaken for a record. Note that `106` covers both a missing key and a genuine permission gap: both fall back to the guest account, and the message names which.
* **Records are keyed by row id, not returned as an array.** A list answers `{"12345": {…}, "12346": {…}}`, and each row repeats its id in `_ragicId`. Field keys are field names by default; pass `naming: 'EID'` for field IDs.

Writing, in practice:

* **Use `schema.get` first.** A write needs field IDs, and a form's fields differ per account. `schema.get` returns them with their types and, for a select field, up to 50 options — but it needs an **account manager's** API key, so a non-admin key gets `403 no right`.
* **Subtable rows are grouped by row id.** On create, give every field of one row the same *negative* id under `_subtable_<id>` so Ragic can tell which values belong together. On update, use the *positive* row id you read from the record. To delete a row, send `_DELSUB_<subtableId>` with an array of row ids.
* **Dates are `yyyy/MM/dd` or `yyyy/MM/dd HH:mm:ss`**, in filters and in field values, never ISO 8601.
* **`where` is a three-part filter.** `{ fieldId, op, value }` becomes `fieldId,op,value`, and several conditions are sent as a repeated `where` parameter. A comma or `%` in the value is encoded the way Ragic expects, so pass the value as plain text. Repeated `eq`/`regex`/`like` on one field is an **OR**; a `gte` with an `lte` on one field is a **range (AND)**. Leave `value` out to match an empty field.
* **A listing returns 1,000 records by default.** Set `limit` explicitly for more, and `subtables: false` when you do not need them, which makes the response far smaller.
* **Writes run more than the save.** Workflows, AI Agents and notifications run by default; `doWorkflow: false`, `doAIAgent: false` and `notification: false` turn them off, and `doValidation: true` refuses a save that breaks the sheet's validation rules. `checkLock: true` refuses to edit a locked record.
* **`records.lock` / `records.unlock`** map to Ragic's bare `lock` / `unlock` query flag on a POST to the record, and need the same permission as locking in the form. An update with `checkLock: true` on a locked record fails with code `402`; without it Ragic saves the update anyway, so a lock only guards updates that ask for the check.
* **`records.delete` moves the record to the sheet's recycle bin**, where it can be restored from the Ragic UI. Reading a deleted or unknown id answers an empty object rather than an error.
* **A write answers `{ status, msg, ragicId, data }`**: `ragicId` is the record id and `data` is the saved record keyed by field ID. A save that breaks a field rule (a required field left empty, a duplicate key) comes back as `status: "INVALID"` with code `202`, and the plugin throws it.
* **`records.comment`** is sent as multipart form data with Ragic's `comment` flag, which is the only form the comment route accepts, and answers the new comment keyed by its row id.
* **No rate limit, but a queue.** Ragic documents no numeric limit, only a per-account queue of 50 that stops accepting GET requests when full, and a manual review above 5 requests a second. A `429` or body code `204` ("Request Frequency Too High") is retried up to three times, and a 5xx twice, for reads, lock and unlock only; a create, update, comment or delete is never sent twice, since a 5xx can follow a save that already ran its workflows.

**What you get:**

* 9 typed API operations

## Setup

**1. Install**

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

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

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

```bash bun theme={null}
bun add corsair @corsair-dev/ragic 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 { ragic } from '@corsair-dev/ragic';

export const corsair = createCorsair({
	plugins: [
		ragic(),
	],
	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}
> ragic()
> ```
>
> 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: 'ragic',
	tenantId: 'acme',
});
// redirect the user's browser to connectUrl
```

## Example API calls

**List records from a sheet**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.ragic.api.records.list({
	account: 'demo',
	path: '/sales',
	sheetIndex: 1,
	limit: 50,
});
```

**Create a record**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.ragic.api.records.create({
	account: 'demo',
	path: '/sales',
	sheetIndex: 1,
	fields: { 2000123: 'Dunder Mifflin', 2000127: 'Jeff Kuo' },
});
```

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

## What's next

> [card] [API reference](https://docs.corsair.dev/plugins/ragic/api.md)
>
> Every `ragic.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.