# API externa

URL: /desenvolvedores/api

Consulte projetos, reports e evidências do Mira por HTTP, com uma chave de serviço.

A API externa dá a serviços e automações acesso de leitura a projetos, reports, comentários, atividade e evidências. Ela usa uma **identidade de serviço**: um consumidor sem login humano, com projetos autorizados e quota própria. A chave de serviço não escreve, não gerencia usuários e não emite outras chaves.

## Criar o serviço e a chave [#criar-o-serviço-e-a-chave]

**Quem faz:** admin.

<Steps>
<Step>

### Criar a identidade [#criar-a-identidade]

Em **Admin**, abra **Serviços de API** e crie um serviço. Selecione os projetos que ele pode consultar. Sem projetos, ele não vê nenhum report. Defina a quota de requisições por minuto (120 por padrão).

</Step>
<Step>

### Escolher as permissões [#escolher-as-permissões]

Cada serviço tem duas permissões separadas, e uma não libera a outra:

- **Consultas**: as rotas `/api/v1/query/`. Vem ativada.
- **Envio de source maps**: só as rotas `/api/v1/upload/`, usadas pelo `@mira/sourcemaps` no CI. Vem desativada.

Sem a permissão da rota, a resposta é 403. Para o CI, crie um serviço só com o envio de source maps, limitado aos projetos da app.

</Step>
<Step>

### Emitir a chave [#emitir-a-chave]

Abra **Chaves**, informe nome e validade e use **Emitir chave**. O segredo (`mira_svc_...`) aparece só nessa resposta. Salve o segredo no gerenciador de segredos do consumidor, nunca em URLs nem em logs.

</Step>
</Steps>

Para rotacionar, emita a substituta, atualize o consumidor e revogue a antiga. Desativar o serviço revoga todas as chaves, e reativar exige emitir novas.

## Consultar [#consultar]

O prefixo é `/api/v1/query`. Envie `Authorization: Bearer <chave>` por HTTPS.

```bash
curl -H "Authorization: Bearer $MIRA_SERVICE_KEY" \
  "https://app.miraqa.org/api/v1/query/projects"

curl -G -H "Authorization: Bearer $MIRA_SERVICE_KEY" \
  --data-urlencode 'status=new' \
  --data-urlencode 'status=triaged' \
  --data-urlencode 'limit=50' \
  "https://app.miraqa.org/api/v1/query/projects/PROJECT_ID/reports"
```

| GET | Conteúdo |
| --- | --- |
| `/projects` | Projetos autorizados |
| `/projects/:projectId` | Um projeto autorizado |
| `/projects/:projectId/environments` | Ambientes e origens |
| `/projects/:projectId/tags` | Tags, com filtro `q` opcional |
| `/projects/:projectId/reports` | Lista de reports com filtros e cursor |
| `/reports/:reportId` | Report sem URL capturada, contexto técnico ou anexos |
| `/reports/:reportId/comments` | Comentários |
| `/reports/:reportId/activity` | Histórico de atividade |
| `/reports/:reportId/evidence` | URL capturada, contexto e metadados dos anexos |
| `/attachments/:attachmentId/content` | Bytes do anexo |

As duas últimas rotas exigem a permissão de evidências do serviço.

A `color` de uma tag é o nome de uma cor da paleta: `slate`, `red`, `orange`, `yellow`, `green`, `teal`, `blue`, `violet` ou `pink`. Ao criar ou editar uma tag, a API aceita também um hex de 6 dígitos, como `#3b82f6`, e grava a cor da paleta mais próxima.

### Anexos de vídeo e de ações [#anexos-de-vídeo-e-de-ações]

A rota `/query/attachments/:attachmentId/content`, desta API, devolve sempre o arquivo inteiro e ignora `Range`. Quem precisa buscar um trecho do vídeo usa `/api/v1/attachments/:attachmentId/content`, que aceita a sessão do dashboard ou um token pessoal e o cabeçalho `Range`. Uma faixa válida volta com `206` e `Content-Range`, e uma faixa fora do tamanho do arquivo volta com `416`.

Um anexo `recording` leva `metadata` com cinco campos, todos obrigatórios e sem campos extras: `source` (`tab` ou `desktop`), `startedAt` (ISO 8601 com fuso), `durationMs` (inteiro positivo, até 300000), `microphone` e `systemAudio` (booleanos). O arquivo respeita o menor entre o limite do projeto e 100 MiB, e uma gravação acima disso volta com `413`. Um `recording` sem esses campos volta com `400`.

Um anexo `actions` é um JSON `{ "entries": [...] }` com até 200 entradas e 2 MiB. Cada entrada tem `type` (`click`, `navigation`, `reload`, `tab_hidden`, `tab_visible` ou `capture`) e `timestamp`. Um clique leva `selector`, `tag` e, às vezes, `label` com até 80 caracteres. A navegação leva `from` e `to`, e a recarga leva `url`. Campos que não constam aqui, como `value`, são recusados com `400`: nenhuma entrada carrega teclas nem valores de campo.

### Filtros e paginação [#filtros-e-paginação]

A lista de reports aceita `status`, `type`, `priority`, `assigneeId`, `tagId`, `from`, `to`, `q`, `sort`, `cursor` e `limit`. Para vários valores de uma enumeração, repita a chave. Datas são ISO 8601 com fuso. `assigneeId=unassigned` traz os reports sem responsável. `sort` aceita `newest` (padrão), `oldest`, `priority`, `status`, `number_desc` e `number_asc`, e o cursor de uma página só vale na mesma ordenação.

A resposta é `{ items, nextCursor }`. Passe o `nextCursor` sem modificar como `cursor` até receber `null`. O padrão é 50 itens, e o máximo é 100. A ordenação usa a data de criação e o id, e não serve como feed de alterações. As demais listas devolvem o array completo.

<Callout type="warning" title="Dados pessoais">
Mesmo sem a permissão de evidências, reports e comentários podem conter dados pessoais escritos por pessoas, e os reports trazem nome e email do autor e do responsável. Armazene as respostas com o mesmo cuidado que o dashboard.
</Callout>

## Quota [#quota]

Cada serviço tem um limite de requisições por minuto, compartilhado por todas as chaves dele. A resposta traz `X-RateLimit-Limit` e `X-RateLimit-Remaining`. Ao estourar a quota, a API responde 429 com `Retry-After: 60`. A quota conta toda requisição com chave válida e permissão para a rota, inclusive as que terminam em 400 ou 404. Chave inválida (401) e rota fora das permissões do serviço (403) não contam.

## Erros [#erros]

O formato é `{ error: { code, message, details? } }`.

| HTTP | Código | O que fazer |
| --- | --- | --- |
| 400 | `validation_failed` | Corrigir os parâmetros, inclusive o cursor |
| 401 | `unauthorized` | Conferir chave, validade e revogação |
| 403 | `forbidden` | Conferir a permissão de evidências ou a operação |
| 404 | `not_found` | O recurso não existe ou está fora dos projetos autorizados |
| 413 | `payload_too_large` | Reduzir o arquivo enviado, como um source map |
| 429 | `rate_limited` | Esperar o prazo de `Retry-After` |
| 500 | `internal` | Registrar a falha, sem imprimir a chave |

## OpenAPI [#openapi]

- `https://app.miraqa.org/api/v1/query/docs`: documentação HTML com operações, parâmetros, exemplos e schemas.
- `https://app.miraqa.org/api/v1/query/openapi.json`: documento de consulta, para importar em clientes OpenAPI.

## Próximos passos [#próximos-passos]

<Cards>
<Card href="/desenvolvedores/webhooks" title="Payloads de webhook" description="Receba os eventos em vez de consultar em intervalos." />
<Card href="/desenvolvedores/tokens" title="Tokens" description="Compare a chave de serviço com os outros tipos de credencial." />
<Card href="/desenvolvedores/cli" title="CLI" description="Para o trabalho de uma pessoa, a CLI cobre a API inteira." />
</Cards>
