> ## Documentation Index
> Fetch the complete documentation index at: https://docs.get-rial.com/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={"system"}
npm i @rial/sdk
```

Node 20 or newer. No dependencies.

## Setup

```ts theme={"system"}
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={"system"}
const verification = await rial.verifications.create({
  steps: [
    { key: 'front', type: 'image', description: 'Front of the car', expectedObject: 'car' },
    { key: 'plate', type: 'image', description: 'License plate' },
  ],
  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={"system"}
const verification = await rial.verifications.create({
  maxCaptures: 1,
  webhookUrl: 'https://api.acme.com/rial',
});
```

```ts theme={"system"}
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 and the status. `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)`.

## Reading results

```ts theme={"system"}
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={"system"}
const page = await rial.verifications.list({ status: 'completed', limit: 50 });
const next = await rial.verifications.list({ cursor: page.nextCursor });
```

```ts theme={"system"}
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={"system"}
const template = await rial.templates.publish({
  slug: 'car-intake',
  name: 'Car intake',
  steps: [
    { key: 'front', type: 'image', description: 'Front of the car', expectedObject: 'car' },
    { key: 'odometer', type: 'image', description: 'Odometer' },
    { key: 'plate', type: 'text', description: 'License plate number' },
  ],
  expiresInSeconds: 86_400,
});

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

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

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

### One verification per row

```ts theme={"system"}
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={"system"}
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={"system"}
import { readFile } from 'node:fs/promises';

const result = await rial.audits.run({
  file: await readFile('claim-9912.jpg'),
  contentType: 'image/jpeg',
  expectedObject: 'car',
  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.

## Errors

```ts theme={"system"}
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` and `message`. 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.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](/api-reference), so they never drift. Exported types: `Verification`, `LiveVerdict`, `AuditVerdict`, `Step`, `LinkTemplate`, `ImportJob`, `Database`, `VerificationEvent`, `PublicVerification`, `WebhookEvent`, `WebhookVerdict`, and the inputs `CreateVerificationInput`, `PublishTemplateInput`, `ListVerificationsFilters`, `AuditInput`.
