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

# RepairShopr plugin

> Repair desk and ticketing with RepairShopr - read and write customers, tickets, invoices and products, and list contact people and vendors, against a RepairShopr account's own subdomain.

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

Generate a key in RepairShopr under **Admin → API → API Tokens**. The key is specific to the user who created it, and every request also needs the account's **subdomain**, the part before `.repairshopr.com`, because the API is served from `https://<subdomain>.repairshopr.com/api/v1`. Store both. Things to know before you call:

* **The subdomain is half the credential.** Pass it when you create the plugin, or store it on the account beside the key. A missing or malformed value fails before a request is sent rather than resolving to a host that does not exist.
* **A 401 covers three different problems.** `Failed to authenticate account` means a bad key, a wrong subdomain, or a key that belongs to another account; RepairShopr does not separate them, so the plugin reports one auth error rather than guessing.
* **Paging is by page number.** Collections answer with their own array plus a `meta` block carrying `total_pages`, and `next_page` is derived from it. Only `invoices` accepts `per_page` (at most 25); the others use the account default.
* **Sorting names a column and a direction**, such as `firstname ASC` or `created_at DESC`.
* **Products and vendors may carry no `meta`.** Without it the plugin cannot tell whether more pages exist, so `next_page` is null rather than an invented number. A null there does not mean every record came back: ask for the next `page` yourself and stop when it comes back empty.
* **Money is a string on invoices.** `subtotal`, `total` and `tax` come back as decimal strings such as `"181.50"`, while product prices are numbers.
* **A contact spells its address differently from a customer.** A contact has `address1`/`address2`; a customer has `address`/`address_2`. Both are modelled as the API sends them.
* **Ticket status is the account's own list.** `status` takes any status name the account uses; the defaults are `New`, `In Progress`, `Resolved`, `Invoiced`, `Waiting for Parts`, `Waiting on Customer`, `Scheduled` and `Customer Reply`. `priority` takes one of `Urgent`, `High`, `Medium` or `Low`.
* **Creating a product needs a description.** RepairShopr rejects a product without one, so the schema requires it rather than letting the API return `Name can't be blank`.
* **A rejection echoes what you sent.** A `422` body carries every message at fault in `message` and the offending values in `params`, both exposed on the error.
* **Deletes are permanent.** `customers.delete` and `tickets.delete` have no undo and are marked destructive.
* **Retries.** RepairShopr documents no throughput window and no `Retry-After`, so a 429 is retried three times with exponential backoff. A `5xx` is retried only for a call the plugin declared read-only.

**What you get:**

* 18 typed API operations

## Setup

**1. Install**

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

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

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

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

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

## Example API calls

**List a customer's open tickets**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.repairshopr.api.tickets.list({ customer_id: 1, status: 'New' });
```

**Create a repair ticket**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.repairshopr.api.tickets.create({ customer_id: 1, subject: 'Laptop will not boot' });
```

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

## What's next

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