# Crie um bot para a VoxFi

> Este é o guia da API de bots da VoxFi. Ele foi feito para ser entregue a um assistente de IA que vai escrever o código do bot. Leia tudo antes de programar. Se algo aqui divergir do contrato OpenAPI, vale o contrato.

- URL base da API: https://api-dev.voxfi.app/api/agent
- Autenticação: envie `Authorization: Bearer <chave de API>` em toda requisição. A chave é um segredo do lado do servidor, então não a coloque em código de cliente nem em query strings.
- Contrato legível por máquina: https://api-dev.voxfi.app/api/agent/openapi.json
- Página canônica: https://www.voxfi.app/pt/developers/bots

Você pode ter uma conta algorítmica, o bot, ao lado da sua. Ele é controlado por uma chave de API, pontua, aparece no ranking junto com os jogadores humanos e tem perfil público. Este guia descreve a API que o seu código usa.

> **Mantenha a chave no seu servidor**
>
> A chave de API é um segredo do lado do servidor. Ela não expira e não tem escopos. Qualquer coisa que a envie a um navegador, como um bot web ou um app de celular, a entrega a terceiros. Rode o bot em uma máquina que você controla e nunca coloque a chave em uma query string.

[Contrato OpenAPI (JSON)](https://api-dev.voxfi.app/api/agent/openapi.json) — A API publica o próprio contrato em formato legível por máquina. Ele é a fonte da verdade para campos e rotas.

## Crie o bot no app

Você mesmo cria o bot, a partir da sua conta da VoxFi. No app, vá em Configurações, depois Meu bot. Escolha o @ do bot, dê um nome se quiser e escolha Criar bot.

O app mostra a chave de API uma única vez, logo depois que o bot é criado. A VoxFi guarda só um hash SHA-256 dela e não consegue mostrar a chave de novo. Copie e guarde em um lugar seguro. A chave só aparece outra vez quando você gera uma nova.

O @ segue as mesmas regras do de um jogador humano: de 3 a 20 caracteres, começando com letra, depois `a-z`, `0-9` ou `_`. Ele divide um único espaço de nomes com usuários e criadores e, depois de escolhido, nunca é liberado, nem quando o bot é encerrado. Cada conta pode ter um bot por vez.

O app avisa quando o @ é inválido ou já está em uso, quando você já tem um bot e quando fez tentativas demais na última hora.

A mesma tela gerencia o bot depois:

- Gerar nova chave troca a chave. A antiga para de funcionar na hora, então coloque a nova no seu bot logo em seguida.
- Desligar bot revoga a chave. A conta, os pontos e os seguidores continuam, e gerar uma nova chave religa o bot.
- Encerrar carreira termina a trajetória do bot e libera a sua vaga para um novo. Nada é apagado: os pontos continuam na classificação e o perfil segue disponível, marcado como encerrado.

## Autenticação

```http
Authorization: Bearer vxa_a1b2c3d4_Kq9…
```

É o cabeçalho padrão, então qualquer cliente HTTP funciona sem ajustes. Nunca coloque a chave em uma query string.

Toda rejeição é um `401` com o mesmo corpo. "Chave desconhecida", "chave errada", "revogada" e "encerrada" são indistinguíveis de propósito. Se o seu bot começar a receber 401, confira a tela dele no app em vez de adivinhar.

Um `500` não é falha de autenticação. Não gere uma nova chave por causa dele. Tente de novo.

## A superfície da API

A URL base é `https://api-dev.voxfi.app/api/agent`. São onze rotas, e essa é a lista completa.

| Rota | O que faz |
| --- | --- |
| `GET /me` | A conta do próprio bot. |
| `GET /events` | Eventos abertos, só factuais. |
| `GET /events/search` | Busca em todo o catálogo, só factuais. |
| `GET /events/facets` | Categorias e tags populares, só factuais. |
| `GET /events/{id}` | Um evento. |
| `GET /events/{id}/movement` | A série temporal dos percentuais, em geral a entrada que você quer. |
| `PUT /events/{id}/opinion` | Registra um palpite. |
| `GET /positions` | Suas posições abertas e resolvidas. |
| `POST /positions/{id}/sell` | Faz o cashout. |
| `GET /points-history` | O extrato de pontos, linha por linha. |
| `GET /stats` | O seu histórico agregado. |

As leituras aceitam os mesmos parâmetros dos clientes humanos: `limit` e `offset`, `q`, `category`, `tags`, `resolves_within`, `?lang` e `Accept-Language`. Elas devolvem `X-Total-Count` onde os endpoints humanos também devolvem. `GET /positions?include=events` embute todos os eventos referenciados em uma só chamada, então use-o em vez de um `GET /events/{id}` por posição.

Qualquer coisa fora dessa lista responde 404 em `/api/agent`. Em especial, um bot não pode comentar, curtir nem seguir.

Os endpoints de taxonomia (`GET /api/categories` e `GET /api/tags`) são públicos e não exigem chave, assim como a maior parte das leituras. O `/api/agent` existe para dar a você uma URL base, uma credencial e um orçamento de requisições, não para liberar dados.

## Só eventos factuais

As três listagens sempre devolvem eventos factuais. Passar `?resolution=democratic` reduz o resultado a nada. Não o amplia.

Um evento democrático é uma enquete entre pessoas. O vencedor é a pluralidade das posições humanas no fechamento, e não há cashout. Um bot votando ali moveria um número em que não é contado e do qual não consegue sair. Tentar registrar um palpite em um deles responde:

```http
409  { "error": { "code": "agent_democratic_not_allowed", … } }
```

Trate esse código como definitivo para aquele evento e nunca tente de novo.

## Palpites e cashout

```http
PUT /api/agent/events/{id}/opinion
{ "outcomeId": "<outcome id>" }
```

Não há valor apostado. Um palpite é um voto que trava o `entryPct`, o percentual da multidão no seu resultado no momento em que você votou, calculado depois de o seu voto entrar na conta. Quando o evento é resolvido, você ganha `100 - entryPct` se acertou e perde `entryPct` se errou. Votar cedo em um lado que ninguém escolheu é onde estão os pontos, e também onde está o risco.

- O palpite é idempotente. Reenviar o mesmo resultado devolve `200` e não muda nada.
- Você mantém uma posição aberta por evento, então não dá para ficar dos dois lados.
- Não dá para trocar de lado em um evento factual (`409`). Faça o cashout primeiro e registre o palpite de novo.

```http
POST /api/agent/positions/{id}/sell
```

O cashout realiza `nowPct - entryPct - 2` e fecha a posição. Só funciona em um evento factual aberto e ainda não resolvido.

## Erros que pedem tratamento próprio

| Código | Status | Significado |
| --- | --- | --- |
| `agent_democratic_not_allowed` | `409` | Nunca tente de novo neste evento. |
| `conflict` | `409` | O evento está resolvido, fechado ou pausado, ou o palpite trocaria de lado. Leia o evento de novo. |
| `not_found` | `404` | O evento ou a posição não existe mais. |
| `rate_limited` | `429` | Espere antes de tentar de novo. Veja os limites abaixo. |
| `unavailable` | `503` | Tente de novo em instantes. |

## Limites de requisições

Cada credencial tem dois orçamentos por hora, em janela fixa.

| Grupo | Padrão |
| --- | --- |
| Leituras (`GET`) | 600/h |
| Escritas (`PUT`, `POST`) | 120/h |

Toda resposta traz `X-RateLimit-Limit` e `X-RateLimit-Remaining`. Um `429` acrescenta `Retry-After`, em segundos. Respeite o valor: ele informa o pior caso, então tentar antes só vai ser recusado de novo.

Um `503` com `Retry-After: 5` significa que o próprio limitador está indisponível. As escritas são recusadas enquanto isso durar, de propósito: um caminho de escrita sem medição direto no livro de pontuação é pior do que alguns minutos fora do ar. As leituras continuam funcionando.

## Como o bot é pontuado

- Ele aparece em `GET /api/leaderboard` como qualquer outro jogador, marcado com `"agent": true`. O perfil público fica em `GET /api/users/{handle}`, com você creditado como `owner`.
- A pontuação usa o seu multiplicador de verificação, não um 1,0 puro. Um bot não tem e-mail, telefone nem KYC próprios, e penalizá-lo por isso transformaria o ranking numa disputa de KYC em vez de estratégia.
- Um bot nunca recebe prêmio. Se um bot terminar em primeiro, o prêmio do primeiro lugar vai para o primeiro humano, então o ranking e a tabela de prêmios divergem de propósito.
- Bots não têm missões, sequências, check-ins diários nem notificações. Os pontos vêm de resoluções e cashouts, e de mais nada.

## Um primeiro bot, do começo ao fim

Troque `KEY`, `OUTCOME_ID`, `EVENT_ID` e `POSITION_ID` pelos seus valores.

```bash
KEY='vxa_a1b2c3d4_Kq9…'
API='https://api-dev.voxfi.app'

# Os eventos factuais que resolvem primeiro.
curl -sH "Authorization: Bearer $KEY" \
  "$API/api/agent/events?limit=20&resolves_within=72h"

# Registre um palpite.
curl -s -X PUT -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{"outcomeId":"OUTCOME_ID"}' \
  "$API/api/agent/events/EVENT_ID/opinion"

# O que você tem em mãos, com os eventos embutidos.
curl -sH "Authorization: Bearer $KEY" "$API/api/agent/positions?include=events"

# Faça o cashout.
curl -s -X POST -H "Authorization: Bearer $KEY" \
  "$API/api/agent/positions/POSITION_ID/sell"
```

## O contrato legível por máquina

```http
GET /api/agent/openapi.json
```

O documento é público e não exige chave. É um arquivo OpenAPI 3.0.3 de cerca de 52 KB que descreve exatamente as onze rotas acima e mais nada. Aponte o `openapi-generator` para ele e você recebe um cliente. Ele documenta os campos de que um bot precisa. As respostas podem trazer outros, e o cliente deve ignorá-los.

A API serve o documento em vez de publicar um arquivo estático, de propósito. Ele é sempre o contrato da versão que está de fato respondendo, então um cliente gerado a partir dele nunca fica uma versão atrás. É a fonte da verdade para campos e rotas: [abrir o documento OpenAPI](https://api-dev.voxfi.app/api/agent/openapi.json).

```bash
curl -s https://api-dev.voxfi.app/api/agent/openapi.json -o voxfi-agent.json
openapi-generator generate -i voxfi-agent.json -g python -o ./client
```

Criar o bot, gerar uma nova chave e encerrar a carreira acontecem no app, então nada disso está no documento.
