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

# Node / TypeScript SDK

> Create verifications, publish templates and read results from your backend.

```bash theme={"dark"}
npm i @rial/sdk
```

Node 20 or newer. No dependencies.

## Setup

```ts theme={"dark"}
import { createRial } from '@rial/sdk';

const rial = createRial({ apiKey: process.env.RIAL_API_KEY });
```

The key is your secret key from the dashboard (Settings → API). Keep it on the server.

## Your first verdict

```ts theme={"dark"}
const verification = await rial.verifications.create({
  steps: [
    {
      key: 'front',
      label: 'Front',
      type: 'image',
      description: 'Please take a photo of the front of your 2021 Toyota Corolla',
      expectedObject: '2021 Toyota Corolla',
    },
    {
      key: 'plate',
      label: 'License plate',
      type: 'image',
      description: 'Please take a photo of the rear license plate of your Corolla',
    },
  ],
  expectedLocation: 'Av. Córdoba 5635, Buenos Aires',
  metadata: { claimId: 'CLM-9912' },
});

await sms.send(customer.phone, verification.captureUrl);

const result = await rial.verifications.waitFor(verification.id);

if (result.verdict?.rial) {
  return approve(result);
}
return review(result, result.verdict?.signals); // e.g. ['screen_detected']
```

Keep `verification.id` on your side: the API does not echo `metadata` back.

`waitFor` polls until the verification is `completed`, `expired` or `failed`. It backs off on rate limits and gives up after fifteen minutes unless you pass `timeoutMs`.

## Webhooks instead of waiting

```ts theme={"dark"}
const verification = await rial.verifications.create({
  maxCaptures: 1,
  webhookUrl: 'https://api.acme.com/rial',
});
```

```ts theme={"dark"}
import { parseWebhookEvent } from '@rial/sdk';

app.post('/rial', async (req, res) => {
  const event = parseWebhookEvent(req.body);

  if (event.type === 'verification.completed') {
    await claims.attachVerdict(event.verificationId, event.verdict);
  }

  res.sendStatus(200);
});
```

Two events arrive. `verification.created` carries the ids, the status, your `metadata` and the `templateSlug` when it came from a template. `verification.completed` adds the verdict in its detailed form (`label`, `score`, `reasonCode`, one entry per check), the captures and the answers. For anything else, call `verifications.get(event.verificationId)`.

Treat the event as a notification. Before you act on it, confirm with `verifications.get(event.verificationId)`.

## Reading results

```ts theme={"dark"}
const verification = await rial.verifications.get('vfy_01H…');

verification.status;                 // 'completed'
verification.verdict?.rial;          // true
verification.verdict?.signals;       // []
verification.objectMatch?.status;    // 'match'
verification.locationMatch?.status;  // 'verified'
verification.condition?.score;       // 8.5
```

```ts theme={"dark"}
const page = await rial.verifications.list({ status: 'completed', limit: 50 });
const next = await rial.verifications.list({ cursor: page.nextCursor });
```

```ts theme={"dark"}
for await (const verification of rial.verifications.iterate({ verdict: 'suspicious' })) {
  await queue.push(verification.id);
}
```

`list` filters: `status`, `verdict`, `reasonCode`, `from`, `to`, `mode`, `metadata` (matches the values you sent at creation), `limit`, `cursor`.

## Templates

A template is a set of steps published once, with a permanent link.

```ts theme={"dark"}
const template = await rial.templates.publish({
  slug: 'car-intake',
  name: 'Car intake',
  steps: [
    {
      key: 'front',
      label: 'Front',
      type: 'image',
      description: 'Please take a photo of the front of your 2021 Toyota Corolla',
      expectedObject: '2021 Toyota Corolla',
    },
    {
      key: 'odometer',
      label: 'Odometer',
      type: 'image',
      description: 'Please take a photo of the odometer showing the mileage',
    },
    {
      key: 'plate',
      label: 'License plate',
      type: 'text',
      description: 'Please type your license plate number',
    },
  ],
  expiresInSeconds: 86_400,
});

template.publicUrl;  // send this to anyone; each person gets their own verification
```

```ts theme={"dark"}
await rial.templates.update('car-intake', { status: 'paused' });
await rial.templates.update('car-intake', { status: 'active' });
await rial.templates.delete('car-intake');
```

```ts theme={"dark"}
const test = await rial.templates.simulate('car-intake');
test.captureUrl;  // a real run that is not billed and sends no notifications
```

### One link for one person

```ts theme={"dark"}
const verification = await rial.templates.mint('car-intake', {
  identification: 'POL-001',
  webhookUrl: 'https://your.app/rial',
  metadata: { claimId: 'CLM-9912' },
});

verification.captureUrl;  // single-use, for that person only
```

Everything in the input overrides the template for that one verification: `webhookUrl`, `expiresInSeconds`, `notifyEmail` (`null` opts out), `brandSlug`; `metadata` merges, yours wins.

### One verification per row

```ts theme={"dark"}
const preview = await rial.templates.previewCsv('car-intake', { csv });

if (preview.invalidRowsTotal > 0) {
  throw new Error(`${preview.invalidRowsTotal} rows need fixing`);
}

const job = await rial.templates.importCsv('car-intake', { csv });
const finished = await rial.templates.waitForImport(job.id);

finished.importedRows;  // one verification created per row
```

## Databases

Rows your templates compare against: a policy number the person types finds their row, and the expected location or object come from it.

```ts theme={"dark"}
const database = await rial.databases.create({
  name: 'Insured fleet',
  columns: ['policy', 'plate', 'address'],
  rows: [
    ['POL-001', 'AB123CD', 'Av. Córdoba 5635, Buenos Aires'],
    ['POL-002', 'EF456GH', 'Av. Santa Fe 1200, Buenos Aires'],
  ],
  identifierColumn: 'policy',
});

await rial.databases.replace(database.id, { name: 'Insured fleet', columns, rows });
await rial.databases.rename(database.id, 'Insured fleet 2026');
```

## Audits: a photo you already have

No link and no camera: you have the file, Rial analyses it. With no device behind the photo there is no `rial` flag; you get the checks that tripped.

```ts theme={"dark"}
import { readFile } from 'node:fs/promises';

const result = await rial.audits.run({
  file: await readFile('claim-9912.jpg'),
  contentType: 'image/jpeg',
  expectedObject: '2021 Toyota Corolla',
  conditionAspects: ['paint', 'bumper'],
  metadata: { claimId: 'CLM-9912' },
});

result.verdict?.signals;      // [] or e.g. [{ type: 'ai_detected', confidence: 0.81 }]
result.objectMatch?.status;   // 'match'
result.condition?.score;      // 7.5
```

`audits.run` uploads, starts the analysis and waits. `audits.create` does the first two and returns at once; read the result later with `verifications.get` or a webhook. Add `checks: ['ai']` to run AI detection alone — see [Only AI detection](https://docs.rial.io/audits.md#only-ai-detection).

## Errors

```ts theme={"dark"}
import { RialError } from '@rial/sdk';

try {
  await rial.templates.publish({ slug: 'car-intake', name: 'Car intake', steps });
} catch (error) {
  if (error instanceof RialError && error.code === 'slug_taken') {
    return rial.templates.update('car-intake', { steps });
  }
  throw error;
}
```

`RialError` carries `status`, `code`, `fields`, `hint` and a `message` that already says what was wrong: `rial API responded 400 (invalid_request): steps.0.conditionAspects: must be a list, got string`. Every code and what to do about it: [Errors](https://docs.rial.io/errors.md). Reads retry on network errors, `429` and `5xx`; writes never retry on their own.

## Reference

| Method | Endpoint |
| - | - |
| `verifications.create(input)` | `POST /v1/verifications` |
| `verifications.get(id)` | `GET /v1/verifications/{id}` |
| `verifications.list(filters)` | `GET /v1/verifications` |
| `verifications.iterate(filters)` | `GET /v1/verifications`, page by page |
| `verifications.waitFor(id, options)` | `GET /v1/verifications/{id}`, polled |
| `verifications.finalize(id, { narrative })` | `POST /v1/verifications/{id}/finalize` |
| `verifications.events(id)` | `GET /v1/verifications/{id}/events` |
| `verifications.publicResult(id)` | `GET /v1/verifications/{id}/public` |
| `templates.list()` | `GET /v1/link-templates` |
| `templates.publish(input)` | `POST /v1/link-templates` |
| `templates.update(slug, patch)` | `PATCH /v1/link-templates/{slug}` |
| `templates.delete(slug)` | `DELETE /v1/link-templates/{slug}` |
| `templates.simulate(slug)` | `POST /v1/link-templates/{slug}/simulate` |
| `templates.mint(slug, input)` | `POST /v1/link-templates/{slug}/verifications` |
| `templates.previewCsv(slug, input)` | `POST /v1/link-templates/{slug}/import/preview` |
| `templates.importCsv(slug, input)` | `POST /v1/link-templates/{slug}/import` |
| `templates.getImport(jobId)` | `GET /v1/link-templates/imports/{jobId}` |
| `templates.waitForImport(jobId, options)` | `GET /v1/link-templates/imports/{jobId}`, polled |
| `databases.list()` | `GET /v1/databases` |
| `databases.create(input)` | `POST /v1/databases` |
| `databases.replace(id, input)` | `PUT /v1/databases/{id}` |
| `databases.rename(id, name)` | `PATCH /v1/databases/{id}` |
| `audits.create(input)` | `POST /v1/verifications` in audit mode, then the upload |
| `audits.run(input, options)` | `audits.create`, then `verifications.waitFor` |

Inputs and results use camelCase and are typed from the same OpenAPI document as the [API reference](https://docs.rial.io/api-reference), so they never drift. Exported types: `Verification`, `LiveVerdict`, `AuditVerdict`, `Step`, `LinkTemplate`, `ImportJob`, `Database`, `VerificationEvent`, `PublicVerification`, `WebhookEvent`, `WebhookVerdict`, and the inputs `CreateVerificationInput`, `PublishTemplateInput`, `MintFromTemplateInput`, `ListVerificationsFilters`, `AuditInput`, `AuditCheck`.


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