{"openapi":"3.0.0","paths":{"/api/collections/cache":{"post":{"description":"Retorna o cache válido. Se a coleção estiver ausente ou vencida, busca todas as páginas na Pokémon TCG API e atualiza o PostgreSQL. Importações simultâneas do mesmo nome são deduplicadas.","operationId":"CollectionsController_cache","parameters":[{"name":"name","required":false,"in":"query","description":"Nome internacional ou alias conhecido da coleção.","schema":{"minLength":1,"maxLength":120,"example":"Perfect Order","type":"string"}},{"name":"collection","required":false,"in":"query","description":"Alias de `name`. Aceito por compatibilidade.","schema":{"minLength":1,"maxLength":120,"example":"Heróis Excelsos","type":"string"}},{"name":"force","required":false,"in":"query","description":"Ignora o TTL e força uma nova sincronização.","schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"description":"Coleção encontrada no cache (`hit`) ou último cache retornado após falha de atualização (`stale`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionResponseDto"}}}},"201":{"description":"Coleção importada e armazenada pela primeira vez.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionResponseDto"}}}}},"summary":"Obtém ou importa uma coleção","tags":["Coleções"]}},"/api/cards/price":{"get":{"description":"Consulta primeiro o PostgreSQL. Se a coleção ainda não existir no cache, ela é importada automaticamente. Use preferencialmente `number`; pesquisas somente por nome podem ser ambíguas.","operationId":"CardsController_price","parameters":[{"name":"collection","required":true,"in":"query","description":"Nome da coleção.","schema":{"minLength":1,"maxLength":120,"example":"Perfect Order","type":"string"}},{"name":"number","required":false,"in":"query","description":"Número impresso. Aceita apenas o numerador ou o formato completo.","schema":{"example":"062","type":"string"}},{"name":"name","required":false,"in":"query","description":"Nome exato da carta. Use quando o número não estiver disponível.","schema":{"example":"Meowth ex","type":"string"}}],"responses":{"200":{"description":"Carta encontrada. O preço pode ser nulo quando o Cardmarket não fornecer média de 30 dias.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardPriceResponseDto"}}}}},"summary":"Consulta uma carta com a média de 30 dias","tags":["Cartas"]}},"/api/cards/grade":{"post":{"operationId":"GradingController_grade","parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["front"],"properties":{"front":{"type":"string","format":"binary","description":"Frente da carta (JPEG/PNG, máx. 15MB)"},"back":{"type":"string","format":"binary","description":"Verso da carta (JPEG/PNG, máx. 15MB) — opcional mas recomendado"}}}}}},"responses":{"200":{"description":"Grading concluído"},"202":{"description":"Ainda processando — consulte pollUrl"},"400":{"description":"Imagem inválida ou ausente"},"502":{"description":"Falha no serviço de grading"},"503":{"description":"Créditos insuficientes"}},"summary":"Envia imagem da carta para grading por IA (aguarda até 120s)","tags":["Cartas"]}},"/api/cards/grade/{scanId}":{"get":{"operationId":"GradingController_pollScan","parameters":[{"name":"scanId","required":true,"in":"path","description":"ID do scan retornado no 202","schema":{"type":"number"}}],"responses":{"200":{"description":"Grading concluído"},"202":{"description":"Ainda processando"},"404":{"description":"Scan não encontrado"}},"summary":"Consulta resultado de um scan de grading em andamento","tags":["Cartas"]}},"/health":{"get":{"operationId":"HealthController_health","parameters":[],"responses":{"200":{"description":""}},"summary":"Verifica a saúde do servidor","tags":["Sistema"]}},"/api/trial/status":{"post":{"operationId":"TrialController_status","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrialDeviceDto"}}}},"responses":{"200":{"description":"Saldo atual do aparelho"}},"summary":"Consulta o saldo do trial de 20 requisições","tags":["Trial"]}},"/api/trial/consume":{"post":{"operationId":"TrialController_consume","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrialDeviceDto"}}}},"responses":{"200":{"description":"Saldo após a tentativa de consumo"}},"summary":"Consome atomicamente uma requisição do trial","tags":["Trial"]}}},"info":{"title":"NaCuia TCG API","description":"API unificada para catálogo Pokémon TCG e médias de 30 dias do Cardmarket, com cache persistente em PostgreSQL.","version":"1.0.0","contact":{}},"tags":[{"name":"Sistema","description":""},{"name":"Coleções","description":""},{"name":"Cartas","description":""},{"name":"Trial","description":""}],"servers":[],"components":{"schemas":{"CollectionSummaryDto":{"type":"object","properties":{"id":{"type":"string","example":"me3"},"name":{"type":"string","example":"Perfect Order"},"series":{"type":"string","nullable":true,"example":"Mega Evolution"},"printedTotal":{"type":"number","nullable":true,"example":88},"total":{"type":"number","nullable":true,"example":124},"cardCount":{"type":"number","minimum":0,"example":124},"syncedAt":{"type":"string","format":"date-time","example":"2026-08-28T13:00:00.000Z"},"cache":{"type":"string","enum":["hit","miss","stale"],"description":"`hit`: cache válido; `miss`: importado/atualizado; `stale`: último cache retornado porque a atualização falhou.","example":"hit"},"warning":{"type":"string","description":"Presente somente quando um cache antigo é devolvido.","example":"A atualização falhou; retornando o último cache válido."}},"required":["id","name","series","printedTotal","total","cardCount","syncedAt","cache"]},"CollectionResponseDto":{"type":"object","properties":{"collection":{"$ref":"#/components/schemas/CollectionSummaryDto"}},"required":["collection"]},"CardImagesDto":{"type":"object","properties":{"small":{"type":"string","nullable":true,"format":"uri"},"large":{"type":"string","nullable":true,"format":"uri"}},"required":["small","large"]},"CardSummaryDto":{"type":"object","properties":{"id":{"type":"string","example":"me3-62"},"name":{"type":"string","example":"Meowth ex"},"number":{"type":"string","example":"062"},"rarity":{"type":"string","nullable":true,"example":"Double Rare"},"images":{"$ref":"#/components/schemas/CardImagesDto"}},"required":["id","name","number","rarity","images"]},"CardmarketPriceDto":{"type":"object","properties":{"avg30":{"type":"number","nullable":true,"format":"double","description":"Média de venda não Reverse Holo dos últimos 30 dias.","example":12.5},"reverseHoloAvg30":{"type":"number","nullable":true,"format":"double","description":"Média Reverse Holo dos últimos 30 dias.","example":null},"currency":{"type":"string","nullable":true,"enum":["EUR",null],"example":"EUR"},"source":{"type":"string","enum":["cardmarket"],"example":"cardmarket"},"sourceUpdatedAt":{"type":"string","nullable":true,"description":"Data informada pela fonte.","example":"2026/08/27"},"cachedAt":{"type":"string","format":"date-time","example":"2026-08-28T13:00:00.000Z"}},"required":["avg30","reverseHoloAvg30","currency","source","sourceUpdatedAt","cachedAt"]},"CardPriceResponseDto":{"type":"object","properties":{"card":{"$ref":"#/components/schemas/CardSummaryDto"},"price":{"$ref":"#/components/schemas/CardmarketPriceDto"},"collectionCache":{"type":"string","enum":["hit","miss","stale"],"description":"`hit`: cache válido; `miss`: importado/atualizado; `stale`: último cache retornado porque a atualização falhou.","example":"hit"},"cardCache":{"type":"string","enum":["hit","miss","stale"],"description":"`hit`: preço dentro do TTL; `miss`: preço atualizado na fonte; `stale`: preço anterior mantido porque a atualização falhou.","example":"hit"}},"required":["card","price","collectionCache","cardCache"]},"TrialDeviceDto":{"type":"object","properties":{"platform":{"type":"string","enum":["android","ios"],"example":"android"},"deviceId":{"type":"string","description":"Identificador nativo do aparelho","minLength":8,"maxLength":200}},"required":["platform","deviceId"]}}}}