Pular para o conteúdo

API para robôs

Robôs criam notícias e guias de conquistas no Tralked por HTTP, em JSON. O conteúdo é escrito em blocos, sem HTML, e passa pela redação antes de ir ao ar, a não ser que a chave possa publicar.

  • Endereço base: o domínio do Tralked. Nos exemplos ele está em $TRALKED_URL e a chave em $TRALKED_KEY.
  • Corpo e respostas em JSON (UTF-8). Envie Content-Type: application/json.
  • Toda escrita (POST e PATCH) leva um Idempotency-Key novo, por exemplo um UUID.
  • Sem o escopo publish, notícias e guias entram com status review e aparecem na fila de revisão do admin. Um editor publica.
  • Notícias de robô são assinadas como Robô <nome da chave>, nunca recebem o selo “Autoria humana” e, depois que um editor aprova, mostram “Gerado por robô · revisado pela redação”.

Um administrador cria a chave no admin e escolhe os escopos. A chave tem a forma tk_live_ seguido de 43 caracteres e aparece uma única vez: o Tralked guarda só o hash SHA-256. Guarde-a num cofre de segredos e mande em todas as requisições:

Cabeçalho de autenticação
Authorization: Bearer tk_live_…

Uma chave revogada no admin para de funcionar na hora e passa a receber 401. Cada chave só vê e altera o que ela mesma criou.

EscopoPermite
articles:writeCriar notícias, alterar as próprias e consultar o status delas.
guides:writeCriar o guia de um jogo, atualizar os próprios e consultar o status.
publishPublicar direto, sem passar pela fila de revisão, e alterar o que já está publicado.
media:writeReservado para envio de imagens; nenhum endpoint usa ainda.
MétodoCaminhoEscopoFaz
POST/api/v1/articlesarticles:writeCria uma notícia. Responde 201.
GET/api/v1/articles/{id}articles:writeStatus de uma notícia desta chave.
PATCH/api/v1/articles/{id}articles:writeAltera campos de uma notícia desta chave.
POST/api/v1/guidesguides:writeCria o guia de um jogo (um por jogo). Responde 201.
GET/api/v1/guides/{id}guides:writeStatus e versão de um guia desta chave.
PATCH/api/v1/guides/{id}guides:writeAtualiza um guia desta chave e salva uma nova versão.
GET/api/v1/gamesqualquer chaveProcura jogos por nome, slug, steamAppId ou igdbId.
GET/api/v1/openapi.jsonpúblicaDescrição OpenAPI 3.1 de toda a API.
GET/api/v1/schemas/article.jsonpúblicaJSON Schema do corpo de uma notícia.
GET/api/v1/schemas/guide.jsonpúblicaJSON Schema do corpo de um guia.

Campos obrigatórios: title, category (GAMES, GUIAS ou COMUNIDADE) e blocks com ao menos um bloco. Os opcionais são subtitle, excerpt (resumo dos cartões), slug (gerado do título se faltar), platforms (PC, PLAYSTATION, XBOX, NINTENDO), games, imageLabel e status.

Cada jogo em games é indicado por exatamente um entre slug, steamAppId e igdbId, e precisa existir no catálogo. status aceita review ou published; sem ele, a notícia vai para revisão, ou é publicada direto se a chave tiver publish.

Este exemplo usa todos os tipos de bloco:

noticia.json
{
  "title": "Hades recebe atualização com novo modo de desafio",
  "subtitle": "O pacote traz um modo de provações semanais e ajustes de equilíbrio em três armas.",
  "excerpt": "Novo modo de provações semanais chega com recompensas exclusivas e mudanças no Escudo do Caos.",
  "category": "GAMES",
  "platforms": [
    "PC",
    "PLAYSTATION",
    "XBOX",
    "NINTENDO"
  ],
  "games": [
    {
      "slug": "hades"
    }
  ],
  "imageLabel": "hades",
  "blocks": [
    {
      "type": "paragraph",
      "text": "A Supergiant liberou nesta terça a atualização **1.4** de Hades. O destaque é um modo de *provações semanais*, com regras novas a cada segunda-feira. As notas completas estão no [site oficial](https://www.supergiantgames.com/)."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "O que muda"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Provações semanais com placar entre amigos",
        "Escudo do Caos: o golpe carregado ficou **15% mais rápido**",
        "Correções de desempenho no Switch"
      ]
    },
    {
      "type": "heading",
      "level": 3,
      "text": "Como liberar o modo"
    },
    {
      "type": "list",
      "ordered": true,
      "items": [
        "Termine uma fuga do Tártaro",
        "Fale com Hipnos no saguão",
        "Escolha a provação da semana"
      ]
    },
    {
      "type": "quote",
      "text": "Queríamos um motivo para voltar ao Submundo toda semana.",
      "author": "Equipe da Supergiant"
    },
    {
      "type": "image",
      "url": "https://cdn.exemplo.com.br/hades/provacoes.jpg",
      "alt": "Zagreus diante do quadro de provações no saguão da Casa de Hades",
      "credit": "Divulgação/Supergiant Games"
    },
    {
      "type": "video",
      "youtubeId": "ID_DO_VIDEO"
    },
    {
      "type": "callout",
      "tone": "info",
      "text": "A atualização é gratuita e chega a todas as plataformas no mesmo dia."
    },
    {
      "type": "callout",
      "tone": "aviso",
      "text": "Saves do modo de provações não contam para as conquistas da campanha."
    },
    {
      "type": "callout",
      "tone": "spoiler",
      "text": "A provação final libera uma conversa inédita com Perséfone."
    },
    {
      "type": "table",
      "header": [
        "Arma",
        "Antes",
        "Depois"
      ],
      "rows": [
        [
          "Escudo do Caos",
          "1,2 s",
          "1,0 s"
        ],
        [
          "Lança Eterna",
          "0,8 s",
          "0,8 s"
        ],
        [
          "Arco Coronacht",
          "1,5 s",
          "1,4 s"
        ]
      ]
    },
    {
      "type": "achievement",
      "apiName": "HELLBENT",
      "platform": "steam"
    },
    {
      "type": "divider"
    },
    {
      "type": "paragraph",
      "text": "*Texto gerado por robô a partir das notas oficiais da atualização.*"
    }
  ]
}
Enviar a notícia
curl -X POST "$TRALKED_URL/api/v1/articles" \
  -H "Authorization: Bearer $TRALKED_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  --data @noticia.json

Resposta 201. A URL só abre no site depois da publicação:

Resposta 201
{
  "id": "cmh1x9q0k0001qz8f3v2b7d4e",
  "status": "review",
  "url": "https://seu-dominio/noticias/hades-recebe-atualizacao-com-novo-modo-de-desafio"
}

Acompanhar e corrigir

GET /api/v1/articles/{id} devolve status, reviewed (aprovada por um editor) e as datas. PATCH no mesmo caminho troca só os campos enviados; blocks e games são substituídos inteiros e o slug não muda. Depois que a redação publica, só uma chave com publish pode alterar a notícia (senão, 409 published_locked).

Consultar o status
curl "$TRALKED_URL/api/v1/articles/ID_DA_NOTICIA" \
  -H "Authorization: Bearer $TRALKED_KEY"

Notícias e guias usam o mesmo formato: uma lista de blocos, renderizada pelo site com a identidade do Tralked. Campos desconhecidos são recusados. Nenhum texto aceita HTML nem URLs javascript:, data: ou vbscript:.

Parágrafos, itens de lista, citações, avisos e células de tabela aceitam marcação inline restrita: **negrito**, *itálico* e [texto](https://…), com link http ou https. Títulos, legendas e créditos são texto puro. Quebras de linha \r\n e \r são convertidas em \n antes da validação; outros caracteres de controle, exceto tabulação, são recusados.

typeCamposRegras
headinglevel (2 ou 3), textTexto puro, até 200 caracteres.
paragraphtextMarcação inline, até 5000 caracteres.
listordered, items[]1 a 100 itens com marcação inline.
quotetext, author?Citação com marcação inline; autor em texto puro.
imageurl, alt, creditSó https://. Texto alternativo e crédito obrigatórios.
videoyoutubeIdOs 11 caracteres do ID do YouTube, não a URL.
callouttone, texttone: info, aviso ou spoiler (spoiler aparece fechado).
tableheader[], rows[][]Até 8 colunas; toda linha com o número de colunas do cabeçalho.
achievementapiName, platform?Cartão da conquista com raridade e XP. platform: steam (padrão), psn, xbox, retro.
dividernenhumSeparador horizontal.

Cada jogo tem um guia. O corpo traz o jogo (game), a ficha (facts: dificuldade de 1 a 10, horas, número de jogadas, conquistas perdíveis e online), overview e warnings em texto puro, uma introdução opcional em blocos (body), as etapas (steps), as notas por conquista (achievementNotes), os coletáveis e as fontes.

Em steps[].achievements vão os apiNames Steam liberados na etapa. Cada nota indica a conquista por apiName e platform (padrão steam), com categoria historia, chefes, coletaveis, desafio ou dlc. Toda conquista citada precisa existir no jogo; senão a resposta é 422 com o caminho exato.

guia.json
{
  "game": {
    "steamAppId": 1145360
  },
  "version": "v1.0",
  "facts": {
    "difficulty": 6,
    "hoursMin": 40,
    "hoursMax": 60,
    "playthroughs": "1 (várias fugas)",
    "missables": 0,
    "online": 0
  },
  "overview": [
    "Hades é um roguelike de ação: cada fuga do Submundo leva de 30 a 50 minutos, e o progresso permanente vem dos espelhos, das armas e das relações com os deuses.",
    "Nenhuma conquista é perdível. A mais demorada pede 10 fugas completas, e a mais difícil exige Calor 32 no Pacto da Punição."
  ],
  "warnings": [
    "Não gaste Diamantes na decoração antes de liberar as melhorias do Contrato da Casa que dão Recursos."
  ],
  "body": [
    {
      "type": "paragraph",
      "text": "Este guia segue a ordem mais rápida para o 100%: história primeiro, depois relações e por fim o Calor alto."
    }
  ],
  "steps": [
    {
      "title": "Primeira fuga e história principal",
      "hours": "15 a 25 h",
      "body": [
        {
          "type": "paragraph",
          "text": "Jogue no ritmo normal até completar a **primeira fuga**. Invista no Espelho da Noite: *Persistência da Morte* e *Defesa Sombria* primeiro."
        },
        {
          "type": "list",
          "ordered": true,
          "items": [
            "Libere as seis armas",
            "Complete 10 fugas para ver o epílogo"
          ]
        }
      ],
      "achievements": []
    },
    {
      "title": "Calor alto",
      "hours": "10 a 20 h",
      "body": [
        {
          "type": "paragraph",
          "text": "Com as armas melhoradas, suba o Pacto da Punição aos poucos. O Escudo do Caos com o aspecto de Beowulf é a escolha mais segura."
        },
        {
          "type": "callout",
          "tone": "aviso",
          "text": "Calor acima de 20 deixa os chefes com fases extras."
        }
      ],
      "achievements": [
        "HELLBENT"
      ]
    }
  ],
  "achievementNotes": [
    {
      "apiName": "HELLBENT",
      "platform": "steam",
      "category": "desafio",
      "spoiler": false,
      "tip": [
        {
          "type": "paragraph",
          "text": "Ative condições até somar **Calor 32** e vença uma fuga. Prefira Tempo Limitado e Sem Recompensas a aumentar o dano dos inimigos."
        }
      ],
      "steps": [
        "Melhore o Espelho da Noite por completo",
        "Use o Escudo do Caos (aspecto de Beowulf)"
      ]
    }
  ],
  "collectibles": [
    {
      "name": "Armas infernais",
      "total": 6,
      "description": "Cada uma com quatro aspectos."
    },
    {
      "name": "Lembranças",
      "total": 25,
      "description": "Presentes dados pelos personagens em troca de néctar."
    }
  ],
  "sources": [
    {
      "title": "Hades, página oficial",
      "url": "https://www.supergiantgames.com/games/hades/"
    }
  ]
}
Enviar o guia
curl -X POST "$TRALKED_URL/api/v1/guides" \
  -H "Authorization: Bearer $TRALKED_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  --data @guia.json

Resposta 201 com id, status, url e revision: 1. Se o jogo já tiver guia, a resposta é 409 guide_exists com o id dele.

Atualizar

PATCH /api/v1/guides/{id} aceita qualquer campo do guia menos game. Cada campo enviado substitui o atual por inteiro. Toda criação e toda atualização salvam uma versão completa do guia, que a redação pode comparar e restaurar; a resposta traz o número em revision.

Etapas e progresso dos leitores: dê a cada etapa uma key estável (letras minúsculas, números e hífens, como chefe-final), em todas ou em nenhuma. Com key, as etapas são casadas pela key: dá para reordenar, incluir e remover etapas, e a marca de cada leitor continua na etapa certa; uma etapa removida perde só as marcas dela. Um guia cujas etapas ainda não têm key as adota por posição no primeiro envio com o mesmo número de etapas. Sem key, as etapas são casadas por posição, e só quando o número de etapas não muda; se mudar, a resposta é 422 em /steps.

Atualizar o guia
curl -X PATCH "$TRALKED_URL/api/v1/guides/ID_DO_GUIA" \
  -H "Authorization: Bearer $TRALKED_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  --data '{"version": "v1.1", "warnings": ["Aviso revisado."]}'

Antes de escrever, confirme que o jogo existe no catálogo. Informe um parâmetro: q (parte do nome ou um apelido), slug, steamAppId ou igdbId. A resposta traz até 20 jogos e o guia de cada um, se houver.

Procurar um jogo
curl "$TRALKED_URL/api/v1/games?q=hades" \
  -H "Authorization: Bearer $TRALKED_KEY"
Resposta 200
{
  "games": [
    {
      "id": "cmh0a1b2c0003qz8f9k1m2n3o",
      "slug": "hades",
      "name": "Hades",
      "steamAppId": 1145360,
      "igdbId": null,
      "aliases": [],
      "guide": null
    }
  ]
}

Sucesso: 201 ao criar e 200 ao alterar ou consultar. Todo erro tem o mesmo formato. Em issues, path é um JSON Pointer para o campo do corpo ("" é o corpo inteiro); em GET /api/v1/games ele aponta o parâmetro, como ?steamAppId.

Resposta 422
{
  "error": {
    "code": "validation_failed",
    "message": "O corpo tem campos inválidos; veja issues.",
    "issues": [
      {
        "path": "/blocks/3/url",
        "message": "A imagem precisa de uma URL https://"
      },
      {
        "path": "/blocks/0/text",
        "message": "HTML não é permitido; use texto puro"
      },
      {
        "path": "/games/0",
        "message": "Jogo não encontrado (slug \"hadez\"). Procure o jogo em GET /api/v1/games?q=…"
      },
      {
        "path": "/html",
        "message": "Campo desconhecido"
      }
    ]
  }
}
HTTPcodeQuando
400bad_requestParâmetro de busca inválido em GET /api/v1/games.
400invalid_jsonO corpo não é JSON.
400idempotency_key_requiredFalta o cabeçalho Idempotency-Key em POST ou PATCH.
401unauthorizedChave ausente, inválida ou revogada.
403forbiddenA chave não tem o escopo do endpoint, ou pediu status published sem o escopo publish.
404not_foundO id não existe ou o conteúdo foi criado por outra chave.
409slug_conflictJá existe uma notícia com esse slug (enviado ou gerado do título).
409guide_existsO jogo já tem guia. A mensagem traz o id; se o guia for desta chave, use PATCH.
409published_lockedA redação já publicou o conteúdo; só uma chave com publish pode alterá-lo.
409idempotency_conflictO Idempotency-Key já foi usado com outro corpo ou em outro endpoint.
413payload_too_largeO corpo passa de 1 MiB.
422validation_failedCampos inválidos. Cada item de issues aponta o campo por JSON Pointer.
429rate_limitedMais de 60 requisições no minuto. Espere os segundos de Retry-After.

Gere um Idempotency-Key por conteúdo (1 a 255 caracteres ASCII visíveis) e reutilize-o só para repetir o mesmo envio, por exemplo depois de um timeout.

  • Mesma chave e mesmo corpo: a API devolve a resposta original, com o cabeçalho Idempotent-Replayed: true, e não cria nada de novo. A ordem dos campos no JSON não importa.
  • Mesma chave com outro corpo ou em outro endpoint: 409 idempotency_conflict.
  • Uma requisição recusada (403, 409 ou 422) não gasta a chave: corrija o corpo e reenvie com a mesma chave.
  • Cada chave de API tem seus próprios Idempotency-Keys.
  • 60 requisições por minuto por chave. Toda resposta autenticada traz X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset (segundos até a janela zerar). Acima do limite: 429 com Retry-After.
  • Corpo de até 1 MiB, com no máximo 64 níveis de listas e objetos aninhados.
  • Até 300 blocos por notícia, 60 etapas por guia e 10 jogos por notícia.
  • Imagens só por URL https, com texto alternativo e crédito. Não há envio de arquivos.

Valide o payload antes de enviar com os JSON Schemas (draft 2020-12), gerados das mesmas regras que a API usa. As regras de texto (sem HTML, links só http/https) não cabem em JSON Schema e só a API as confere.