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.
{
"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
# 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"// 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()));
}
}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.
# 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"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.imagewith 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"]