Game Images API

Game Images API

Versión: 1.1 URL base (público): https://grabbr.jailbroken.tech Local: http://127.0.0.1:8790 Content-Type: application/json; charset=utf-8 (salvo media y HTML) Auth: ninguna CORS: Access-Control-Allow-Origin: * en todas las respuestas. OPTIONS permitido (GET, POST, OPTIONS, header Content-Type).

Con un nombre de juego y un proveedor, la API toma siempre el arte de lobby de Stake (thumbnailUrl en mediumrare/imgix), lo recorta en modo cover y devuelve archivos WebP en tamaños de píxel configurables. El GraphQL de esa búsqueda sale siempre por IP de Chile.

Tamaños por defecto: 400×400, 400×300, 300×400. Cualquier tamaño extra (por ejemplo 900×1200) se agrega por query, body, env o config.json — el renderer no limita cuántos tamaños pedís.


Contenido

  1. Inicio rápido
  2. Convenciones
  3. GET /v1/images
  4. POST /v1/images
  5. GET /v1/info
  6. Modo de información extendida
  7. Esquemas de respuesta
  8. GET /media/{key}/{WxH}.webp
  9. GET /v1/providers

9b. GET /v1/suggest

  1. GET /v1/sizes
  2. GET /health
  3. UI de prueba
  4. Errores
  5. Proveedores y alias
  6. Cómo se elige la fuente
  7. Configuración de tamaños
  8. Pipeline de imagen
  9. Caché y URLs públicas
  10. Arranque y entorno
  11. Catálogo
  12. Ejemplo medido — Azure Roulette
  13. Ejemplo medido — Blood Suckers (extendido)
  14. OpenAPI

Inicio rápido

# Objeto completo (juego + source + images[])
curl -sS 'https://grabbr.jailbroken.tech/v1/images?name=azure%20roulette&provider=pragmatic%20play%20live'

# Solo el array de las tres imágenes
curl -sS 'https://grabbr.jailbroken.tech/v1/images?name=azure%20roulette&provider=pragmatic%20play%20live&format=array'

# Tamaño extra
curl -sS 'https://grabbr.jailbroken.tech/v1/images?name=azure%20roulette&provider=pragmatic%20play%20live&sizes=400x400,400x300,300x400,900x1200'

# Stats extendidos (RTP, varianza, max win, …) + imágenes oficiales
curl -sS 'https://grabbr.jailbroken.tech/v1/images?name=Blood%20Suckers&provider=NetEnt&mode=extended'

# Solo stats
curl -sS 'https://grabbr.jailbroken.tech/v1/info?name=Blood%20Suckers&provider=NetEnt'

# POST
curl -sS -X POST https://grabbr.jailbroken.tech/v1/images \
  -H 'content-type: application/json' \
  -d '{"name":"azure roulette","provider":"Pragmatic Play Live"}'

Documentación en inglés: API.en.md · OpenAPI: openapi.yaml


Convenciones

ReglaDetalle
Campo de juegoQuery/body name o game (mismo significado).
Campo de proveedorTexto libre. Match case-insensitive contra id, label y alias.
String de tamañoANCHOxALTO, solo dígitos, x insensible a mayúsculas. Rango 16–4000 por lado. Varios tamaños: separados por coma o espacio.
Tamaños defaultDe config.json → sizes, override con GAME_IMAGES_SIZES o sizes por request.
Formato de salidaSiempre WebP. Default recorte cover (centro, LANCZOS). Crop opcional del original grabado antes. Calidad default 82.
Array de imágenesUna entrada por tamaño pedido, mismo orden que la lista.
format=arrayEl body HTTP 200 es solo el array images.
URL públicaurl = {publicBase}/media/{key}/{W}x{H}.webp. path es lo mismo sin host.
Schemehttp salvo que el request mande X-Forwarded-Proto: https.
Cache-Controlpublic, max-age=86400 en HTTP 200; no-store en errores.

GET /v1/images

Resuelve arte oficial y devuelve variantes WebP.

URL: /v1/images

Query parameters

NombreRequeridoTipoDescripción
namesí*stringTítulo del juego, p. ej. azure roulette.
gamesí*stringAlias de name. Se usa si name está vacío.
providersístringLabel o alias del proveedor, p. ej. pragmatic play live.
sizesnostringOverride de tamaños, p. ej. 400x400,400x300,300x400 o 900x1200.
formatnostringarray → el body es solo el array de imágenes. Cualquier otro valor → objeto completo. En este modo no se incluye info.
modenostringextended (también info / full) agrega stats de SlotCatalog en info. Las imágenes siguen siendo oficiales, sin marca de agua.
extendednostring1 / true / yes — igual que mode=extended.
cropnostringCaja opcional sobre el original grabado: x,y,ancho,alto. Si se omite, cover centrado.
cropUnitnostringpx o ratio (0–1). Si se omite: los cuatro valores ≤ 1 → ratio, si no píxeles.
cropX cropY cropW cropHnonumberLa misma caja en cuatro query params.

\* Uno de name o game tiene que venir con valor.

El crop opcional se aplica al original de Stake antes del cover-fit de cada tamaño. Las variantes auto quedan en /media/{key}/{W}x{H}.webp. Las recortadas usan /media/{key}/{cropToken}/{W}x{H}.webp. El original sin tocar: GET /media/{key}/original. La respuesta incluye original y crop (null si no se usó).

Ejemplo

GET /v1/images?name=azure%20roulette&provider=pragmatic%20play%20live HTTP/1.1
Host: 127.0.0.1:8790

El body 200 medido está en ejemplo medido.


POST /v1/images

Mismo handler que GET. Body JSON.

URL: /v1/images Header: Content-Type: application/json

Body

{
  "name": "azure roulette",
  "game": "opcional, se usa si falta name",
  "provider": "Pragmatic Play Live",
  "sizes": "400x400,400x300,300x400",
  "format": "array",
  "crop": { "x": 40, "y": 20, "width": 280, "height": 280, "unit": "px" }
}
CampoRequeridoTipoDescripción
namesí*stringTítulo del juego.
gamesí*stringAlias de name.
providersístringProveedor.
sizesnostring o array"400x400,900x1200" o [{ "id": "poster", "width": 900, "height": 1200 }, "400x400"].
formatnostring"array" para un array pelado.
modenostring"extended" para incluir info.
extendednobooleantrue — igual que mode: "extended".
cropnoobject o stringOpcional. `{ "x", "y", "width", "height", "unit": "px"

JSON inválido → 400 { "error": "invalid json" }.

curl -sS -X POST https://grabbr.jailbroken.tech/v1/images \
  -H 'content-type: application/json' \
  -d '{
    "name": "Gates of Olympus",
    "provider": "Pragmatic Play",
    "sizes": [{"id":"square","width":400,"height":400},{"id":"poster","width":900,"height":1200}]
  }'

GET /v1/info

Solo metadata estilo SlotCatalog. Sin imágenes.

URL: /v1/info También POST /v1/info con JSON { "name", "provider" }.

NombreRequeridoTipoDescripción
name / gamesístringTítulo. Se prueban alias y orden invertido (azure roulette → Roulette Azure).
providernostringAyuda a elegir la fila correcta (NetEnt vs un remake Megaways).

HTTP 200 si hay página; 404 { "info": { "found": false, … } } si el search no pega.

curl -sS 'https://grabbr.jailbroken.tech/v1/info?name=Blood%20Suckers&provider=NetEnt'

Modo de información extendida

El mismo tipo de ficha que la página de SlotCatalog (proveedor, RTP, varianza, max win, betways, fecha de salida, más type / layout / apuestas / features / themes / rating si están).

Las imágenes siempre salen de Stake (thumbnailUrl / imgix). Los thumbs de SlotCatalog llevan marca de agua y no se usan. El host slotcatalog.com está prohibido como fuente de imagen.

Cómo activarloEjemplo
Querymode=extended o extended=1 en /v1/images
Body JSON"mode": "extended" o "extended": true
Solo statsGET /v1/info

Si SlotCatalog no tiene hit, los stats pueden salir de Stake o de un stub generated. images[] sigue siendo solo Stake. HTTP 404 cuando Stake no tiene thumbnail. format=array sigue siendo solo imágenes.

Search: typeahead SlotCatalog (/json.php?blck=topSrch). Se parsea la tabla #attributes y el JSON-LD VideoGame. HTML cacheado en cache/slotcatalog/ 7 días. Fallback de metadata+thumb: Stake kuratorGameQuery, siempre por IP de Chile.


Esquemas de respuesta

Objeto completo (format omitido)

{
  "game": "Roulette Azure",
  "query": "azure roulette",
  "provider": "pragmatic-play-live",
  "providerLabel": "Pragmatic Play Live",
  "source": {
    "url": "https://mediumrare.imgix.net/....",
    "kind": "stake-thumbnail",
    "page": "https://stake1017.com/casino/games/..."
  },
  "images": [ { "...": "ImageObject" } ]
}
CampoTipoDescripción
gamestringTítulo de catálogo si existe; si no, el nombre consultado.
querystringNombre que enviaste.
providerstringId canónico del proveedor (slug).
providerLabelstringLabel humano.
source.urlstringURL absoluta del original descargado.
source.kindstringCómo se encontró. Ver kinds de fuente.
source.symbolstring o nullId del juego en el proveedor (PP 227, vs20olympgate, …).
source.pagestring o nullPágina de marketing/catálogo si se scrapeo HTML.
imagesarrayUn WebP por tamaño pedido.
modestringSi extendido: "extended".
infoobjectSi extendido. Ver InfoObject.
imagesErrorstringSolo si extendido y falló el arte oficial.

ImageObject (cada ítem de images[])

CampoTipoDescripción
idstringId del tamaño (square, landscape, portrait, o {W}x{H}).
sizestring{width}x{height}
widthintegerAncho de salida en píxeles.
heightintegerAlto de salida en píxeles.
formatstringSiempre "webp".
urlstringURL absoluta del archivo.
pathstringSolo path, p. ej. /media/{key}/400x400.webp.
bytesintegerTamaño en bytes de ese WebP.

InfoObject (info con mode=extended, y /v1/info)

Forma de la tabla de atributos de SlotCatalog. null si la página no tiene valor (N/A → null).

CampoTipoEjemplo Blood Suckers
foundbooleantrue
sourcestring"slotcatalog"
pagestringhttps://slotcatalog.com/en/slots/Blood-Suckers
namestringBlood Suckers
providerNamestringNetEnt
providerPagestringhttps://slotcatalog.com/en/soft/NetEnt
rtpstring98%
rtpValuenumber98
rtpRangesbooleantrue si la página tiene RTP Ranges!
variancestringLow
maxWinstringx1014.00
maxWinValuenumber1014
betwaysstring25
betwaysValueinteger25
releaseDatestring2013-11-10
typestringVideo Slots
layoutstring5-3
minBet / maxBetstring0.25 / 50
features / themesstring[]listas de mecánicas
rating / ratingValuestring / number8.5
cachedbooleanhit de caché en disco

No hay URL de imagen en info. El image del JSON-LD de SlotCatalog se descarta.

Array pelado (format=array)

El body HTTP 200 es ImageObject[] — los mismos tres (o N) ítems, sin wrapper.


GET /media/{key}/original

Original de Stake sin recortar (JPEG/PNG/WebP tal como se bajó). Lo usa el overlay del tester.

GET /media/{key}/{WxH}.webp

WebP binario generado por /v1/images sin crop de usuario (cover centrado).

PiezaRegla
key20 hex ([a-f0-9]{20}). Prefijo SHA-256 de `providerId\
WxHMismo anchoxalto pedido, p. ej. 400x400.

GET /media/{key}/{cropToken}/{WxH}.webp

Mismos tamaños después de un crop opcional. cropToken tipo c-px-40-20-280-280. No pisa los archivos auto.

Headers en 200: Content-Type: image/webp, CORS, Cache-Control: public, max-age=86400.

404 JSON:

Primero llamá /v1/images; después usá url / path de la respuesta. No inventes keys.


GET /v1/suggest

Typeahead de casino Stake (kuratorGameQuery, GraphQL Chile). Lo usa el autocomplete del tester (dispara cada 2 caracteres).

URL: /v1/suggest También POST JSON { "q", "provider?", "limit?" }

ParamReqNotas
qsíAlias query / name. Mínimo 2 caracteres.
providernoPrioriza ese proveedor.
limitnoDefault 20, máx 50.
curl -sS 'https://grabbr.jailbroken.tech/v1/suggest?q=sw&provider=pragmatic-play'

games[]: name, slug, provider, providerLabel, thumbnailUrl, page, type. Lista vacía si no hay match (HTTP 200).


Capture (REA v2)

/v1/images incluye slot.url (página del juego en Stake). Capture abre esa URL en el Chrome Chile, hace click en Play, mapea cada respuesta capturable (iframes + CDN: imágenes, audio, configs, sprites, JS, HTML) y rearmá el árbol host/path como el slot original.

MétodoPathNotas
POST/v1/captureBody { "name", "provider", "slotUrl?" }. HTTP 202 job.
GET/v1/captureJobs recientes.
GET/v1/capture/{id}Status, árbol, kinds, counts.
GET/v1/capture/{id}/bundle.zipZip entero (árbol original + MANIFEST.json).
POST/v1/capture/{id}/bundleBody { "paths": ["host/a.png"] } zip seleccionado.
GET/v1/capture/{id}/file?path=Un archivo.

Poll hasta status done o error. Tester: modo Capture.


GET /v1/providers

Lista ids canónicos, labels y alias.

GET /v1/providers HTTP/1.1
{
  "providers": [
    {
      "id": "pragmatic-play-live",
      "label": "Pragmatic Play Live",
      "aliases": [
        "pragmatic play live",
        "pragmatic live",
        "pp live",
        "pragmaticplaylive",
        "pragmatic play live casino"
      ]
    }
  ]
}

GET /v1/sizes

Lista de tamaños actualmente cargada (config.json + GAME_IMAGES_SIZES).

{
  "sizes": [
    { "id": "square", "width": 400, "height": 400 },
    { "id": "landscape", "width": 400, "height": 300 },
    { "id": "portrait", "width": 300, "height": 400 }
  ]
}

GET /health

Liveness. No chequea CDNs upstream.

{
  "ok": true,
  "service": "game-images-api",
  "images": "stake",
  "chile": { "ok": true, "requireChile": true, "city": "Pucón", "countryCode": "CL" },
  "stakeCdp": true
}

images es siempre "stake". stakeCdp es el Chromium headed (Chile) usado para GraphQL.


UI de prueba

PathTipo
GET /Tester: dropdown de proveedores con buscador + autocomplete de juego cada 2 teclas (Stake) → /v1/images.
GET /index.htmlLa misma página.
GET /docsÍndice de documentación (EN / ES).
GET /docs/enEste documento en HTML (inglés).
GET /docs/esEste documento en HTML (español).
GET /docs/API.en.mdMarkdown en inglés.
GET /docs/API.es.mdMarkdown en español.
GET /openapi.yamlOpenAPI 3.1.
GET /openapi.jsonLa misma spec en JSON.

Errores

Todos los errores son JSON { "error": "..." } más campos extra cuando aplica. Cache-Control: no-store.

HTTPerrorCuándo
400name and provider are requiredFalta name/game o provider.
400unknown providerEl string de proveedor no matcheó. Extra: provider, known (lista de labels).
400invalid jsonEl body POST no es JSON.
400no valid sizes (expected 400x400,400x300,300x400)sizes parseó a lista vacía.
400size out of range: {token}Ancho o alto menor que 16 o mayor que 4000.
404not foundRuta desconocida.
404bad media pathForma de /media/... incorrecta.
404missing variantEl archivo no está en disco.
404no official image found for game='…' provider=…El resolver se quedó sin fuentes. Extra: game, provider (id canónico).
502(mensaje de excepción)Falló descarga, decode o render.

Ejemplo de proveedor desconocido:

{
  "error": "unknown provider",
  "provider": "unknown",
  "known": [
    "Pragmatic Play",
    "Pragmatic Play Live",
    "KAGaming",
    "Ruby Play",
    "Evolution Gaming",
    "Booming Games",
    "100HP",
    "Ezugi",
    "Live Vegas",
    "NetEnt"
  ]
}

Proveedores y alias

Mandá cualquier alias; la API guarda el id.

idLabelAlias
pragmatic-playPragmatic Playpragmatic play, pragmatic, pp, pragmaticplay
pragmatic-play-livePragmatic Play Livepragmatic play live, pragmatic live, pp live, pragmaticplaylive, pragmatic play live casino
kagamingKAGamingka gaming, ka, kaga, kaga88, ka gaming kaga
ruby-playRuby Playrubyplay, ruby play, ruby
evolution-gamingEvolution Gamingevolution, evolution gaming, evo, evolution live
booming-gamesBooming Gamesbooming, booming games
100hp100HP100hp, 100 hp, 100hp gaming, 100 hp gaming
ezugiEzugiezugi, ezugi live, ezugi gaming
live-vegasLive Vegaslive vegas, livevegas, vegas live
netentNetEntnetent, net ent

El match es case-insensitive, id con o sin guiones/espacios, después alias exacto, después contains para alias de longitud ≥ 5.

Dominios oficiales para rankear hits de búsqueda:

idHosts
pragmatic-play, pragmatic-play-livepragmaticplay.com, ppgames.net, pragmaticplay.net
kagamingkaga88.com, kagaming.com
ruby-playrubyplay.com
evolution-gaminggames.evolution.com, evolution.com, egcdn.com
booming-gamesbooming-games.com, boominggames.com
100hp100hp.game, 100hp-games.com, casino.100hp.game
ezugiezugi.com, games.evolution.com
live-vegaslivevegas.live, live-vegas.com
netentnetent.com, netentcloud.com

CDN extra de confianza (después de hosts oficiales): agstatic.com, primeapi.com, softswiss.net.


Cómo se elige la fuente

Las imágenes siempre salen de Stake: thumbnailUrl del lobby (imgix/mediumrare). SlotCatalog, CDN de PP, Bing y páginas de estudio no se usan como fuente de imagen. GraphQL siempre por IP de Chile (Chromium headed). HTTP 404 si Stake no tiene el juego o el thumb no se puede bajar.

Los stats extendidos siguen pudiendo venir de SlotCatalog; las tres WebP siguen siendo Stake.

Kinds de fuente

kindSignificado
stake-thumbnailÚnico kind en vivo: thumbnailUrl de Stake (nunca SlotCatalog).

Configuración de tamaños

Tres capas; gana la última aplicable por request:

  1. config.json → sizes
  2. Env del proceso GAME_IMAGES_SIZES=400x400,400x300,300x400,900x1200
  3. sizes del request (query o JSON)

Ejemplo config.json:

{
  "sizes": [
    { "id": "square", "width": 400, "height": 400 },
    { "id": "landscape", "width": 400, "height": 300 },
    { "id": "portrait", "width": 300, "height": 400 }
  ]
}

Agregar 900×1200 mañana sin tocar código:

GAME_IMAGES_SIZES=400x400,400x300,300x400,900x1200 python3 server.py

O en un solo llamado:

curl -sS 'https://grabbr.jailbroken.tech/v1/images?name=azure%20roulette&provider=pp%20live&sizes=900x1200'

GET /v1/sizes relee config.json en cada request.


Pipeline de imagen

  1. Descarga bytes originales (PNG/JPEG/WebP/GIF).
  2. PIL load + EXIF transpose.
  3. ImageOps.fit cover a (width, height), LANCZOS, centro (0.5, 0.5).
  4. Aplana alpha sobre #0c0c10.
  5. Guarda WebP quality=webpQuality (default 82), method=6.

Cover llena el recuadro; el excedente se recorta. El thumb de Stake (imgix ~800px) se recorta a cada tamaño pedido.


Caché y URLs públicas

PathContenido
cache/originals/{key}.binOriginal descargado.
cache/variants/{key}/{W}x{H}.webpWebP renderizado.

key = sha256(providerId|nombreNormalizado|sourceUrl)[:20].

/v1/images escribe esos archivos y devuelve URLs. /media/... solo lee.

publicBase en config o GAME_IMAGES_PUBLIC_BASE prefija url. Vacío → http(s)://{Host}.

cacheTtlSec en config.json está reservado; no hay eviction. Borrá cache/ para regenerar.


Arranque y entorno

python3 /opt/hallallow/apps/game-images-api/server.py

Bind default: 0.0.0.0:8790. Dependencia: Pillow (requirements.txt).

VariableEfecto
GAME_IMAGES_CONFIGPath al JSON de config (default ./config.json).
GAME_IMAGES_PORTPuerto.
GAME_IMAGES_SIZESLista default, WxH separados por coma.
GAME_IMAGES_PUBLIC_BASEPrefijo absoluto de images[].url (CDN / reverse proxy).

Otras keys de config.json: host, port, publicBase, webpQuality, fit (cover), userAgent, timeoutSec (HTTP upstream, default 18s), sizes.

Detrás de TLS, mandá X-Forwarded-Proto: https para que url salga https://....


Archivo: data/catalog.json. Semilla opcional para que juegos live resuelvan sin página de marketing.

{
  "games": [
    {
      "provider": "pragmatic-play-live",
      "names": ["azure roulette", "roulette azure", "roulette 1 azure"],
      "symbol": "227",
      "title": "Roulette Azure"
    }
  ]
}

Match: nombre exacto (score 100), contains (70+), overlap de tokens (60+). Mínimo 60. Nombres normalizados (minúsculas, guiones → espacios).

Agregá una fila cuando sepas el id PP/Evolution. No hace falta restart; el catálogo se lee en cada resolve.


Ejemplo medido — Azure Roulette

Medido en este host contra la CDN de Pragmatic Play rec/325/227.png.

Request

curl -sS 'https://grabbr.jailbroken.tech/v1/images?name=azure%20roulette&provider=pragmatic%20play%20live'

Respuesta (HTTP 200)

{
  "game": "Roulette Azure",
  "query": "azure roulette",
  "provider": "pragmatic-play-live",
  "providerLabel": "Pragmatic Play Live",
  "source": {
    "url": "https://mediumrare.imgix.net/....",
    "kind": "stake-thumbnail",
    "page": "https://stake1017.com/casino/games/..."
  },
  "images": [
    {
      "id": "square",
      "size": "400x400",
      "width": 400,
      "height": 400,
      "format": "webp",
      "url": "https://grabbr.jailbroken.tech/media/1f976b03a7d9dde1cb54/400x400.webp",
      "path": "/media/1f976b03a7d9dde1cb54/400x400.webp",
      "bytes": 28968
    },
    {
      "id": "landscape",
      "size": "400x300",
      "width": 400,
      "height": 300,
      "format": "webp",
      "url": "https://grabbr.jailbroken.tech/media/1f976b03a7d9dde1cb54/400x300.webp",
      "path": "/media/1f976b03a7d9dde1cb54/400x300.webp",
      "bytes": 26408
    },
    {
      "id": "portrait",
      "size": "300x400",
      "width": 300,
      "height": 400,
      "format": "webp",
      "url": "https://grabbr.jailbroken.tech/media/1f976b03a7d9dde1cb54/300x400.webp",
      "path": "/media/1f976b03a7d9dde1cb54/300x400.webp",
      "bytes": 22324
    }
  ]
}

format=array devuelve solo ese array images. sizes=900x1200,400x400 agregó un WebP 900×1200 (70384 bytes) más 400×400.

Segundo juego medido: Gates of Olympus / Pragmatic Play → vs20olympgate en la misma CDN rec/325, tres WebP con tamaños de píxel exactos.


Ejemplo medido — Blood Suckers (extendido)

Los stats coinciden con la tabla de atributos de Blood Suckers. Las imágenes, si hay, siguen siendo oficiales (nunca /userfiles/image/games/… en slotcatalog.com).

curl -sS 'https://grabbr.jailbroken.tech/v1/images?name=Blood%20Suckers&provider=NetEnt&mode=extended'

info incluye:

CampoValor
ProviderNetEnt (/en/soft/NetEnt)
RTP98% (rtpRanges: true)
VarianceLow
Max Winx1014.00
Betways25
Release Date2013-11-10

Solo stats:

curl -sS 'https://grabbr.jailbroken.tech/v1/info?name=Blood%20Suckers&provider=NetEnt'

OpenAPI

Spec machine-readable: openapi.yaml (también GET /openapi.yaml y GET /openapi.json).

Generar clientes (ejemplo):

npx @redocly/cli build-docs https://grabbr.jailbroken.tech/openapi.yaml