# API Integration Guide

This guide is for server-to-server integrations that want to use Convertrilo as a low-cost video encoding backend.

Do not call Convertrilo directly from browser or mobile apps. Keep Convertrilo API keys, S3 credentials, and Google OAuth tokens on your backend.

## Core Model

1. Your app authenticates your user.
2. Your backend collects or owns the source video location.
3. Your backend calls Convertrilo with an API key.
4. Convertrilo queues one or more encode jobs.
5. Your backend tracks completion by polling job status or receiving managed webhooks.

API users do not need to connect Google Drive in the Convertrilo dashboard. For Google Drive integrations, your app should run its own Google OAuth flow and pass customer-owned `accessToken` and optional `refreshToken` values to Convertrilo.

## Authentication

Create an API key in the dashboard Developer page and send it with every request:

```bash
curl https://api.convertrilo.com/tokens/balance \
  -H "X-API-Key: $CONVERTRILO_API_KEY"
```

Use API keys with the minimum scopes needed by your integration. Most encode integrations need:

- `jobs:create`
- `jobs:read`
- `jobs:cancel` if you expose cancellation
- `credentials:manage` only if your backend manages saved S3 credentials via
  the credential-management endpoints

Errors include a stable `code` field plus a human-readable `message`. Branch on
`code` in your backend instead of parsing messages. See
[`API-ERROR-CODES.md`](API-ERROR-CODES.md).

Most authenticated routes share a global `100 requests/minute` limit. On-demand
encode is stricter at `10 requests/minute`. See
[`API-RATE-LIMITS.md`](API-RATE-LIMITS.md) for endpoint-specific limits.

## TypeScript SDK

```bash
pnpm add @convertrilo/sdk
```

```ts
import { ConvertriloClient } from "@convertrilo/sdk";

const client = new ConvertriloClient({
  baseUrl: "https://api.convertrilo.com",
  apiKey: process.env.CONVERTRILO_API_KEY,
});
```

SDK source and examples: https://github.com/serkandrgn/convertrilo-js

## Flow 1: URL Source To CDN Output

Use this when the source video is already available over HTTP(S), and you want Convertrilo to return a signed CDN download URL.

```ts
const job = await client.onDemandEncode({
  sourceUrl: "https://example.com/input.mp4",
  codec: "h264",
  resolution: "1080p",
  quality: "better",
});

console.log(job.jobId);
```

Equivalent curl:

```bash
curl https://api.convertrilo.com/ondemand/encode \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $CONVERTRILO_API_KEY" \
  -d '{
    "sourceUrl": "https://example.com/input.mp4",
    "codec": "h264",
    "resolution": "1080p",
    "quality": "better"
  }'
```

Poll `/ondemand/status/{jobId}` until the job reaches `success`, then read `downloadUrl`.

## Flow 2: URL Source To S3 Output

Use this when your customer wants the encoded output in their own S3-compatible bucket.

The output credentials need permission to write the final object.

```ts
const job = await client.onDemandEncode({
  sourceUrl: "https://example.com/input.mp4",
  codec: "h264",
  resolution: "1080p",
  outputS3: {
    bucket: "customer-output-bucket",
    key: "encoded/input-1080p.mp4",
    region: "us-east-1",
    endpoint: process.env.CUSTOMER_S3_ENDPOINT,
    accessKeyId: process.env.CUSTOMER_S3_ACCESS_KEY_ID,
    secretAccessKey: process.env.CUSTOMER_S3_SECRET_ACCESS_KEY,
    forcePathStyle: true,
  },
});
```

For AWS S3, `endpoint` is usually unnecessary. For S3-compatible providers such as Cloudflare R2, MinIO, or object storage providers, pass `endpoint` and usually `forcePathStyle: true`.

## Flow 3: S3 Folder To S3 Output

Use this for batch compression. Convertrilo lists a source prefix, filters video files, and queues one encode job per video.

Source credentials need permission to list the prefix and read objects. Output credentials need permission to write encoded objects.

```ts
const batch = await client.onDemandIngestFolder({
  sourceS3: {
    bucket: "customer-source-bucket",
    prefix: "incoming/",
    region: "us-east-1",
    endpoint: process.env.CUSTOMER_S3_ENDPOINT,
    accessKeyId: process.env.CUSTOMER_S3_ACCESS_KEY_ID,
    secretAccessKey: process.env.CUSTOMER_S3_SECRET_ACCESS_KEY,
    forcePathStyle: true,
  },
  outputDestination: "s3",
  outputS3: {
    bucket: "customer-output-bucket",
    prefix: "encoded/",
    region: "us-east-1",
    endpoint: process.env.CUSTOMER_S3_ENDPOINT,
    accessKeyId: process.env.CUSTOMER_S3_ACCESS_KEY_ID,
    secretAccessKey: process.env.CUSTOMER_S3_SECRET_ACCESS_KEY,
    forcePathStyle: true,
  },
  codec: "h264",
  resolution: "1080p",
});

for (const job of batch.jobs || []) {
  console.log(job.jobId, job.fileName);
}
```

Only files with video extensions are queued. If no video files are found, the API returns `404`.

## Flow 4: Google Drive With BYO OAuth Tokens

Use this when your app already owns the customer relationship and can run Google OAuth itself.

Do not send customers to the Convertrilo dashboard OAuth flow for API usage. Your app should request the Google scopes it needs, store refresh tokens on your backend, refresh them with your own Google OAuth client, and pass a fresh access token to Convertrilo per request.

For output-only jobs:

```ts
const job = await client.onDemandEncode({
  sourceUrl: "https://example.com/input.mp4",
  codec: "h264",
  resolution: "1080p",
  outputGoogleDrive: {
    folderId: "GOOGLE_DRIVE_OUTPUT_FOLDER_ID",
    fileName: "input-1080p.mp4",
    accessToken: customerGoogleAccessToken,
  },
});
```

For folder ingest from Google Drive to Google Drive:

```ts
const batch = await client.onDemandIngestFolder({
  sourceGoogleDrive: {
    folderId: "SOURCE_FOLDER_ID",
    accessToken: customerGoogleAccessToken,
  },
  outputDestination: "google-drive",
  outputGoogleDrive: {
    folderId: "OUTPUT_FOLDER_ID",
    accessToken: customerGoogleAccessToken,
  },
  codec: "h264",
  maxFiles: 25,
  resolution: "1080p",
});
```

Use `maxFiles` when you want to cap how many discovered videos are queued from a folder, especially during smoke tests or first-time customer rollouts.

For BYO OAuth, treat Convertrilo as an access-token consumer, not the owner of your Google OAuth refresh flow. Google refresh tokens are bound to the OAuth client that created them, so your backend should refresh customer tokens itself and send a current `accessToken`. Without a valid access token, Google Drive folder ingest returns `401`.

## Tracking Completion

For simple integrations, poll status:

```ts
async function waitForOnDemandJob(jobId: string) {
  while (true) {
    const status = await client.onDemandStatus(jobId);

    if (status.status === "success") return status;
    if (status.status === "failed" || status.status === "canceled") {
      throw new Error(status.failureMessage || `Job ${status.status}`);
    }

    await new Promise((resolve) => setTimeout(resolve, 5000));
  }
}
```

For production workflows, prefer managed webhooks. Managed webhooks are HMAC signed and are better for async pipelines. See `docs/WEBHOOKS.md`.

## Error Handling

Common responses:

- `400`: invalid request payload
- `401`: missing API auth or missing/expired Google Drive token
- `403`: API key does not have the required scope
- `404`: folder ingest found no video files
- `410`: Dropbox source or destination was requested; Dropbox is deprecated

Treat encode job failure separately from request failure. A request can return `200` because the job was queued, then the job can later fail during download, encode, upload, or token refresh.

## Real Infrastructure Smoke Tests

The backend repo includes a smoke test script for real API checks:

```bash
export CONVERTRILO_API_URL="https://api.convertrilo.com"
export CONVERTRILO_API_KEY="cvr_..."
pnpm run smoke:api
```

See `docs/API-SMOKE-TESTING.md` for URL, S3, and Google Drive smoke modes.
