# Convenções

> URL base, formato dos campos, paginação, jobs assíncronos e links que expiram: as regras que valem em toda rota.

As mesmas regras valem em todas as rotas. Leia uma vez e a [referência](/reference) passa a ser previsível.

## URL base

```text
https://api.cut.pro/api/v1
```

Só HTTPS. A versão faz parte do caminho.

## Requisição

Mande o corpo em JSON com `Content-Type: application/json`. As rotas também aceitam `application/x-www-form-urlencoded` e `multipart/form-data` com os mesmos campos, o que ajuda em ferramentas no-code.

## Campos

| Regra | Exemplo |
|---|---|
| Todo campo é `snake_case` | `credits_cost`, `output_resolution` |
| O id do próprio recurso é sempre `id` | `{ "id": "7412908371004912" }` |
| Referência a outro recurso leva o nome dele | `video_id`, `edit_id`, `connection_id` |
| IDs são strings | guarde como string, nunca converta para número |
| Data e hora em ISO 8601, UTC | `"2026-10-29T18:04:51.000Z"` |
| Duração e posição no vídeo em segundos | `"start_time": 42.5` |
| Campo sem valor vem `null`, não some | `"completed_at": null` |

A única exceção é `metadata` em `POST /posts`: as chaves ali são as de cada rede (`privacyLevel`, `categoryId`, `privacyStatus`) e vão para a plataforma do jeito que você escreveu.

## Estado e erro

`status` descreve onde um job está e vem em minúsculas: `queued`, `processing`, `completed`, `failed`.

`code` descreve o que deu errado e vem em maiúsculas: `INSUFFICIENT_CREDITS`, `SUBMISSION_EXPIRED`. Todo erro tem o mesmo corpo, sem mensagem em texto:

```json
{ "code": "INSUFFICIENT_CREDITS", "extra": { "credits_needed": 121, "current_balance": 40 } }
```

Faça `switch` no `code` e traduza para a sua interface. Veja [Erros](/errors).

## Listas e paginação

Toda lista responde com o nome do recurso e um bloco `pagination`:

```json
{
  "clips": [{ "id": "7412908412887301" }],
  "pagination": {
    "current_page": 1,
    "total_pages": 3,
    "total_count": 52,
    "has_next_page": true,
    "has_previous_page": false,
    "limit": 20
  }
}
```

Pagine com `page` (começa em 1) e `limit` (máximo 100). O padrão de `limit` muda por rota e está na referência de cada uma.

```bash
curl "https://api.cut.pro/api/v1/renders?page=2&limit=100" \
  -H "X-Api-Key: $CUTPRO_API_KEY"
```

Avance enquanto `has_next_page` for `true`.

## Jobs assíncronos

Clipagem, render, post, transcrição, geração e persona não terminam na resposta. A criação devolve um `id` e você consulta até o fim:

1. Crie o job e guarde o `id`.
2. Consulte a rota de leitura do job a cada 5 a 10 segundos.
3. Pare quando o `status` chegar a um estado final.

| Job | Rota de leitura | Estados finais |
|---|---|---|
| Clipagem | `GET /clips/{videoId}/submissions/{submissionId}` | `completed`, `failed` |
| Render | `GET /renders/{renderId}` | `completed`, `failed`, `cancelled`, `expired` |
| Post | `GET /posts/{id}` | `completed`, `partial`, `failed`, `cancelled` |
| Transcrição | `GET /transcriptions/{transcriptionId}` | `ready`, `no_speech`, `failed` |
| Geração | `GET /generations/{id}` | `ready`, `failed` |

Esse ritmo cabe com folga no [limite de requisições](/rate-limits). A API não envia webhooks: o fim de um job sempre se descobre consultando.

## Links que expiram

| O quê | Validade |
|---|---|
| `download_url`, `play_url` e `thumbnail_url` de um clipe | curta: peça de novo quando for usar |
| Link de `GET /renders/{renderId}/download` | 1 hora |
| Arquivo MP4 de um render | definido pelo plano, em `render_expiry_hours` de `GET /renders/limits` |
| Edição (`edit_id`) | não expira |

Não guarde link assinado no seu banco. Guarde o `id` e peça o link na hora. Se o MP4 já foi apagado, `POST /renders` com o `edit_id` gera um novo.

## Especificação OpenAPI

A especificação completa fica em [`https://api.cut.pro/api/v1/openapi.json`](https://api.cut.pro/api/v1/openapi.json). Use para gerar um cliente tipado, importar no Postman ou Insomnia, ou dar contexto a um agente de código.
