Cargando tu cuenta

Primeros pasos

De cero a un archivo descargado en cinco pasos. Necesitas una clave de API con los permisos generate y read, y algunas monedas.

1. Elige un modelo

GET /v1/models es público. Cada modelo tiene un id, un tipo de tarea y sus params con los valores que aceptan. Envía solo los parámetros que quieras fijar: los demás toman el valor predeterminado del modelo. Consulta Modelos.

2. Cotiza

POST /v1/quotes devuelve las coins exactas de la solicitud y un quoteToken válido durante 10 minutos.

3. Envía

POST /v1/generations con el mismo modelId y los mismos params, más exactamente uno de quoteToken (cobra exactamente las monedas cotizadas) o maxCoins (cobra el precio actual, nunca más de esto). La cabecera Idempotency-Key es obligatoria: si una solicitud falla por la red, reinténtala con la misma clave y recibirás la misma generación en lugar de un segundo cobro.

Para saltarte la cotización, envía con maxCoins. Si el precio es mayor, la solicitud falla con max_coins_exceeded y no se cobra nada.

JSON
{
  "modelId": "MODEL_ID",
  "params": { "prompt": "A lighthouse at dusk, watercolor" },
  "maxCoins": 40
}

4. Espera el resultado

Consulta GET /v1/generations/:id cada pocos segundos (no más a menudo que cada 2 segundos) hasta que el estado sea succeeded, failed o canceled. Mejor aún, registra un webhook y recibe un aviso.

5. Descarga

Cada elemento de outputs tiene una url. Los enlaces están firmados y caducan pronto: descarga enseguida o vuelve a leer la generación (o GET /v1/assets/:id) para obtener enlaces nuevos.

Ejemplo completo

curl
# 1. List the models (public, no key needed)
curl https://api.inker.si/v1/models

# 2. Quote: the exact coins this request costs, and a quoteToken valid 10 minutes
curl https://api.inker.si/v1/quotes \
  -H "Authorization: Bearer $INKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"modelId": "MODEL_ID", "params": {"prompt": "A lighthouse at dusk, watercolor"}}'

# 3. Submit with the quoteToken (or "maxCoins": N instead).
#    Reuse the same Idempotency-Key when you retry the same request.
curl https://api.inker.si/v1/generations \
  -H "Authorization: Bearer $INKER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"modelId": "MODEL_ID", "params": {"prompt": "A lighthouse at dusk, watercolor"}, "quoteToken": "QUOTE_TOKEN"}'

# 4. Poll until status is succeeded, failed or canceled (or use webhooks)
curl https://api.inker.si/v1/generations/GENERATION_ID \
  -H "Authorization: Bearer $INKER_API_KEY"

# 5. Download an output: outputs[].url is a short-lived signed link
curl -o output.png "OUTPUT_URL"
JavaScript
// Node 18+ (global fetch). Keep the key on your server, never in a browser.
import { writeFile } from 'node:fs/promises';

const API = 'https://api.inker.si';
const KEY = process.env.INKER_API_KEY;

async function inker(path, { headers, ...init } = {}) {
  const response = await fetch(`${API}${path}`, {
    ...init,
    headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json', ...headers },
  });
  const body = await response.json();
  // Errors are RFC 9457 problem details with a stable `code`.
  if (!response.ok) throw new Error(`${body.code}: ${body.detail}`);
  return body;
}

// 1. Pick a model
const { models } = await inker('/v1/models');
const model = models.find((m) => m.taskType === 'image.generate');
const params = { prompt: 'A lighthouse at dusk, watercolor' };

// 2. Quote
const quote = await inker('/v1/quotes', {
  method: 'POST',
  body: JSON.stringify({ modelId: model.id, params }),
});
console.log(`This costs ${quote.coins} coins`);

// 3. Submit (one Idempotency-Key per intended generation; reuse it on retries)
let generation = await inker('/v1/generations', {
  method: 'POST',
  headers: { 'Idempotency-Key': crypto.randomUUID() },
  body: JSON.stringify({ modelId: model.id, params, quoteToken: quote.quoteToken }),
});

// 4. Poll (or receive a webhook)
while (generation.status === 'queued' || generation.status === 'processing') {
  await new Promise((resolve) => setTimeout(resolve, 3000));
  generation = await inker(`/v1/generations/${generation.id}`);
}

// 5. Download the outputs
if (generation.status === 'succeeded') {
  for (const [i, output] of generation.outputs.entries()) {
    const file = await fetch(output.url);
    await writeFile(`output-${i}`, Buffer.from(await file.arrayBuffer()));
  }
}
Python
import os, time, uuid
import requests

API = "https://api.inker.si"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['INKER_API_KEY']}"


def inker(method, path, **kwargs):
    response = session.request(method, API + path, **kwargs)
    body = response.json()
    if not response.ok:
        # Errors are RFC 9457 problem details with a stable "code".
        raise RuntimeError(f"{body['code']}: {body['detail']}")
    return body


# 1. Pick a model
models = inker("GET", "/v1/models")["models"]
model = next(m for m in models if m["taskType"] == "image.generate")
params = {"prompt": "A lighthouse at dusk, watercolor"}

# 2. Quote
quote = inker("POST", "/v1/quotes", json={"modelId": model["id"], "params": params})
print(f"This costs {quote['coins']} coins")

# 3. Submit (one Idempotency-Key per intended generation; reuse it on retries)
generation = inker(
    "POST",
    "/v1/generations",
    headers={"Idempotency-Key": str(uuid.uuid4())},
    json={"modelId": model["id"], "params": params, "quoteToken": quote["quoteToken"]},
)

# 4. Poll (or receive a webhook)
while generation["status"] in ("queued", "processing"):
    time.sleep(3)
    generation = inker("GET", f"/v1/generations/{generation['id']}")

# 5. Download the outputs
if generation["status"] == "succeeded":
    for i, output in enumerate(generation["outputs"]):
        with open(f"output-{i}", "wb") as f:
            f.write(requests.get(output["url"]).content)

Usar tus propios archivos

Los modelos que reciben una imagen, un audio o un vídeo necesitan antes una subida: envía el archivo a POST /v1/uploads como multipart/form-data en el campo file y pasa el id devuelto en el parámetro multimedia (por ejemplo image o images). Las subidas se eliminan a los 30 días.

curl
# Upload an input file (multipart field "file"), then pass its id in a media param
curl https://api.inker.si/v1/uploads \
  -H "Authorization: Bearer $INKER_API_KEY" \
  -F "file=@photo.png"
JavaScript
import { readFile } from 'node:fs/promises';

const form = new FormData();
form.append('file', new Blob([await readFile('photo.png')], { type: 'image/png' }), 'photo.png');
const upload = await fetch('https://api.inker.si/v1/uploads', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.INKER_API_KEY}` },
  body: form,
});
const asset = await upload.json(); // use asset.id in a media param, e.g. params.image
Python
with open("photo.png", "rb") as f:
    asset = inker("POST", "/v1/uploads", files={"file": ("photo.png", f, "image/png")})
# use asset["id"] in a media param, e.g. params["image"]