External generation API. Asynchronous image generation with Flux Klein 4B and Krea 2 Turbo models.
Base URL: https://media.y83.ru/api/v2
OpenAPI spec: openapi.yaml
API v2 is designed for external integrations. All generation is async — you submit a job, receive an ID, then either poll for results or register a webhook.
Two models available:
| Model | Type | Parameters | Steps |
|---|---|---|---|
flux2-klein-4b |
Flux.2 | 4B (GGUF Q4) | 1–8 (default 4) |
krea2-turbo |
Krea 2 | 12B distilled | 1–9 (default 8) |
krea2-turbo also does inpainting and outpainting — pass mask, mask_region
or anypaint_padding, see Inpaint & Outpaint.
All endpoints except /health, /models and /auth/login require a Bearer token.
Option 1 — login endpoint (recommended for integrations):
curl -X POST https://media.y83.ru/api/v2/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "secret", "name": "my-integration"}'
Response:
{
"data": {
"token": "1|abc123def456ghi789...",
"token_type": "Bearer",
"name": "my-integration",
"created_at": "2026-08-28T12:00:00.000Z"
},
"meta": { "api_version": "2.0" }
}
Rate limited: 5 attempts/minute per email+IP, 20/minute per IP.
Option 2 — admin panel: an administrator can create and revoke tokens in the Filament admin (Users → API-токены).
Store the returned plain-text token — it is shown only once. Additional
tokens can be created later with POST /api/v2/tokens using an existing token.
Authorization: Bearer {your_api_token}
100 requests per minute per token.
HTTP headers included in every response:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Max requests per window (100) |
X-RateLimit-Remaining |
Remaining requests in current window |
X-RateLimit-Reset |
Unix timestamp when window resets |
When exceeded, returns 429 Too Many Requests:
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Try again later."
}
}
POST /api/v2/auth/login
Public (rate limited). Exchange email/password for a personal access token.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | yes | Account email |
password |
string | yes | Account password |
name |
string | no | Token name (default api-v2) |
Response (200): see Authentication.
Error (422): wrong credentials — error.details.email.
GET /api/v2/health
Public. No auth required.
Response (200):
{
"status": "ok",
"api_version": "2.0",
"timestamp": "2026-08-28T12:00:00.000Z"
}
curl https://media.y83.ru/api/v2/health
GET /api/v2/models
Public. Returns all models with defaults, limits, and features.
Response (200): See Models section for full structure.
curl https://media.y83.ru/api/v2/models
POST /api/v2/generate
Auth required. Submit an async generation job.
Retry-safe submissions: send an Idempotency-Key header (any stable string,
≤255 chars). Repeating the call with the same key and the same body returns the
stored response of the first call with Idempotency-Replayed: true instead of
queueing a second GPU job; the same key with a different body answers
409 CONFLICT. Keys live for 24 hours. Use it whenever a timeout left you
unsure whether the request was received.
Request body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
model |
string | yes | — | flux2-klein-4b or krea2-turbo |
prompt |
string | yes | — | Text prompt (max 10,000 chars) |
negative_prompt |
string | no | null | What to avoid (max 5,000 chars) |
seed |
integer | no | random | Random seed |
width |
integer | no | 1024 | 256–2048 |
height |
integer | no | 1024 | 256–2048 |
batch_size |
integer | no | 1 | 1–4 images |
steps |
integer | no | model default | Inference steps (flux2 1–8, krea2 1–9) |
cfg |
number | no | 1.0 | Classifier-free guidance (0–30) |
image |
string | no | — | Base64 data URI for img2img (PNG/JPEG/GIF/WebP, ≤2048px). For flux2 switches mode to img2img |
image_url |
string | no | — | Public http(s) URL of the source image (alternative to image, ≤8 MB). Private/loopback/reserved addresses rejected with 422 |
webhook_url |
string | no | — | Callback URL (public http/https only; private/loopback addresses rejected with 422) |
webhook_secret |
string | no | — | HMAC secret |
Krea 2 specific fields:
| Field | Type | Default | Description |
|---|---|---|---|
megapixels |
number | 1.0 | Total megapixels (0.5–2.0) |
krea_lora |
string | — | Primary LoRA ID |
krea_lora_strength |
number | 1.0 | Primary LoRA strength (0–1.5) |
krea_lora_2 |
string | — | Secondary LoRA ID |
krea_lora_2_strength |
number | 1.0 | Secondary LoRA strength (0–1.5) |
krea_4step |
boolean | false | 4-step distilled mode |
style_refs |
array | [] | Up to 12 style references |
concept_sliders |
array | [] | Up to 8 concept sliders |
post_upscale |
string | — | 2x or 4x |
Inpaint / outpaint fields (krea2 only, see Inpaint & Outpaint):
| Field | Type | Description |
|---|---|---|
mask |
string | Base64 mask image, white = repaint |
mask_region |
array | Up to 8 {x, y, w, h} rectangles; the server draws the mask |
anypaint_padding |
object | {left, top, right, bottom} pixels to add around the frame |
denoise |
number | Repaint strength (0–1); omitted → 0.85 inpaint / 1.0 outpaint / 0.45 krea2 img2img. Use 1.0 for a real object replacement |
Response (201):
{
"data": {
"id": "42",
"status": "pending",
"model": "flux2-klein-4b",
"mode": "text2img",
"operation": "text2img",
"created_at": "2026-08-28T12:00:00.000Z"
},
"meta": { "api_version": "2.0" }
}
operation says what the server actually did — text2img, img2img, inpaint,
outpaint or inpaint_outpaint. Clients never have to infer it from the request.
# Basic Flux generation
curl -X POST https://media.y83.ru/api/v2/generate \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{
"model": "flux2-klein-4b",
"prompt": "a beautiful sunset over mountains",
"width": 1024,
"height": 1024
}'
# Krea 2 with LoRA and upscale
curl -X POST https://media.y83.ru/api/v2/generate \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{
"model": "krea2-turbo",
"prompt": "a cozy room with warm lighting, rainy window style",
"krea_lora": "krea2_rainywindow",
"krea_lora_strength": 0.8,
"post_upscale": "2x"
}'
# With webhook
curl -X POST https://media.y83.ru/api/v2/generate \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{
"model": "krea2-turbo",
"prompt": "cyberpunk city at night",
"webhook_url": "https://myapp.com/webhooks/generation",
"webhook_secret": "my_secret_key"
}'
Available for krea2-turbo only. Nothing extra to switch on: passing a mask or
padding is enough, the server picks the AnyPaint workflow and reports it back
in operation.
| Request contains | operation |
Result |
|---|---|---|
mask or mask_region |
inpaint |
Only the masked area is repainted |
anypaint_padding (non-zero) |
outpaint |
Frame is extended, original stays intact |
| both | inpaint_outpaint |
Extended frame + repaint inside the mask |
image/image_url only |
img2img |
Whole image reworked, no mask |
| neither | text2img |
Generation from scratch |
Two ways to say what to repaint:
mask_region — rectangles in pixels. The server renders the mask itself,
so a client needs no image libraries at all:curl -X POST https://media.y83.ru/api/v2/generate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "krea2-turbo",
"prompt": "leather collar, studio light",
"image_url": "https://media.y83.ru/uploads/gen_abc123.png",
"mask_region": [
{ "x": 100, "y": 200, "w": 300, "h": 120 },
{ "x": 700, "y": 40, "w": 120, "h": 120 }
],
"denoise": 1.0
}'
Coordinates are in the source image's own pixels, not the displayed size. A rectangle that leaves the image bounds is rejected with 422 before anything is generated.
mask — your own PNG. Must have the same pixel dimensions as the source
image. White = repaint, black = keep.curl -X POST https://media.y83.ru/api/v2/generate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "krea2-turbo",
"prompt": "remove the scratch, keep everything else",
"image": "data:image/png;base64,...",
"mask": "data:image/png;base64,..."
}'
Outpaint — extend the frame:
curl -X POST https://media.y83.ru/api/v2/generate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "krea2-turbo",
"prompt": "continue the scene, snowy field",
"image_url": "https://media.y83.ru/uploads/gen_abc123.png",
"anypaint_padding": { "left": 0, "top": 128, "right": 256, "bottom": 0 }
}'
Command-line helper for agents and scripts — builds the mask from rectangles, submits, polls and saves the result:
export Y83_API_TOKEN=1|abc123...
php scripts/krea-inpaint.php \
--image=/tmp/cat.png \
--prompt="leather collar" \
--region=100,200,300,120 \
--region=700,40,120,120 \
--out=/tmp/result
# prints: /tmp/result/inpaint_42.png
--dry-run prints the request payload without sending it.
image_url is fetched by the server. If the API host cannot reach its own
public domain (common with hairpin NAT: TLS terminates on a router in front of
the box), links to your own results will time out. Send image as base64, or
use the CLI with a local file — it inlines base64 itself.batch_size must be 1 when a mask or padding is used — AnyPaint produces
a single image. Larger values are rejected with 422.krea_4step is ignored with a mask (AnyPaint needs its own step budget,
max 9). The request still succeeds.denoise defaults: 0.85 with a mask, 1.0 with padding, 0.45 for plain
krea2 img2img (which is also capped at 0.85 — at that point the source is
dropped completely). For inpaint, denoise: 1.0 is honoured and is what you
want for an actual object replacement ("wooden mug" → "ceramic teapot");
0.85 only reworks texture inside the mask.GET /api/v2/generate/{id}
Auth required. Poll for the result — or wait for it in one call.
Query parameters:
| Field | Type | Default | Description |
|---|---|---|---|
wait |
integer | 0 | Long-poll: hold the request while the job runs and answer the moment it becomes terminal (status re-checked every 0.5 s). Allowed range 0–30. meta.waited_ms reports the actual wait. |
A generation takes 20–80 s. Instead of a poll-and-sleep loop, call once with
?wait=30: the request returns as soon as the job is completed/failed, or
with the current status when the 30 s limit expires — then just call it again.
One waiting request occupies one PHP-FPM worker (the host runs
pm.max_children = 5), so do not keep many waits in flight. If you prefer short
calls, the response carries poll_after_ms — the suggested delay before the
next poll.
Every parameter the job was submitted with is echoed back, so this endpoint is enough to repeat or continue a job after a restart:
| Field | Description |
|---|---|
status |
pending, processing, retry_pending, completed, failed, dead_letter |
mode, operation |
operation is text2img, img2img, inpaint, outpaint or inpaint_outpaint — what actually happened |
progress, progress_text, queue_position |
Live progress; queue_position is 1-based and drops to 0 once the job left the queue |
params |
prompt, negative_prompt, seed, steps, cfg, width, height, batch_size, denoise, megapixels, krea_lora(_strength), krea_lora_2(_strength), krea_4step, post_upscale, anypaint_padding |
style_refs |
[{recipe, strength, direction, timing}] — recipes without the reference images |
concept_sliders |
[{name, value, negative_pole, positive_pole}] |
source |
{image_url, mask_url} — feed image_url straight into the next request |
poll_after_ms |
Suggested delay before the next poll (0 for finished jobs) |
Response (200):
Pending:
{
"data": {
"id": "42",
"status": "pending",
"model": "krea2-turbo",
"mode": "krea2",
"operation": "text2img",
"progress": 0,
"progress_text": "В очереди",
"queue_position": 1,
"poll_after_ms": 2000,
"created_at": "2026-08-28T12:00:00.000Z",
"started_at": null,
"completed_at": null,
"params": {
"prompt": "a wooden mug on a kitchen table",
"seed": 1234567890,
"steps": 8,
"cfg": 1.0,
"width": 1024,
"height": 1024,
"batch_size": 1,
"megapixels": 1.0
},
"style_refs": [],
"concept_sliders": [],
"source": null
},
"meta": { "api_version": "2.0", "waited_ms": 0 }
}
Completed:
{
"data": {
"id": "42",
"status": "completed",
"model": "flux2-klein-4b",
"progress": 100,
"created_at": "2026-08-28T12:00:00.000Z",
"completed_at": "2026-08-28T12:00:15.000Z",
"result": {
"images": [
{
"url": "https://media.y83.ru/uploads/gen_abc123.png",
"width": 1024,
"height": 1024
}
],
"seed": 1234567890,
"model": "flux2-klein-4b",
"generation_time_ms": 14523,
"width": 1024,
"height": 1024
}
},
"meta": { "api_version": "2.0" }
}
Failed:
{
"data": {
"id": "42",
"status": "failed",
"model": "krea2-turbo",
"progress": 0,
"created_at": "2026-08-28T12:00:00.000Z",
"completed_at": null,
"error": {
"code": "GENERATION_FAILED",
"message": "Model inference timed out"
}
},
"meta": { "api_version": "2.0" }
}
curl https://media.y83.ru/api/v2/generate/42 \
-H "Authorization: Bearer {api_token}"
# One call instead of a poll loop: waits up to 30 s for the result
curl "https://media.y83.ru/api/v2/generate/42?wait=30" \
-H "Authorization: Bearer {api_token}"
GET /api/v2/generations
Auth required. The caller's own generations, newest first. Unlike the v1 gallery, unfinished jobs are included — an agent that lost a job ID can see the job is still running instead of assuming it vanished and submitting a duplicate.
Query parameters:
| Field | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Page number |
per_page |
integer | 20 | Items per page (1–100) |
status |
string | — | pending, processing, completed, failed, retry_pending, dead_letter |
mode |
string | — | text2img, img2img, krea2 |
model |
string | — | Model ID |
include |
string | — | params adds the parameter echo, progress, operation, source, style_refs and concept_sliders to every item |
Response (200):
{
"data": [
{
"id": "1233",
"status": "completed",
"model": "krea2-turbo",
"mode": "krea2",
"prompt": "a wooden mug on a kitchen table",
"seed": 2560234670541161070,
"created_at": "2026-08-28T12:00:00.000Z",
"result": {
"images": [
{ "url": "https://media.y83.ru/uploads/gen_abc123.png", "width": 1024, "height": 1024 }
],
"seed": 2560234670541161070,
"generation_time_ms": 8432
}
}
],
"meta": { "api_version": "2.0", "page": 1, "per_page": 20, "total": 1, "has_more": false }
}
result is present only for completed jobs. The width/height there are
exactly what you need to compute the coordinates of the next mask_region —
and the url is what you feed into image_url. That closes the loop for
multi-step edits.
With include=params each item also carries the full parameter echo — handy to
rebuild your context after a restart without a status call per job:
curl -s "https://media.y83.ru/api/v2/generations?include=params&per_page=5" \
-H "Authorization: Bearer {api_token}"
# Continue an edit chain: take the last result and repaint a corner of it
LAST=$(curl -s "https://media.y83.ru/api/v2/generations?status=completed&per_page=1" \
-H "Authorization: Bearer $TOKEN" | jq -r '.data[0].result.images[0].url')
curl -X POST https://media.y83.ru/api/v2/generate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg url "$LAST" '{
model: "krea2-turbo",
prompt: "add a second plant on the windowsill",
image_url: $url,
mask_region: [{x: 700, y: 40, w: 160, h: 160}],
denoise: 1.0
}')"
GET /api/v2/quota
Auth required. How much of the 100 requests/minute budget is already spent —
the same numbers as the x-ratelimit-limit / x-ratelimit-remaining headers
sent with every response, so a client can wait before the next call instead of
walking into a 429. The counter includes the /quota request itself.
Response (200):
{
"data": { "limit": 100, "used": 5, "remaining": 95, "resets_in_seconds": 42 },
"meta": { "api_version": "2.0" }
}
curl https://media.y83.ru/api/v2/quota -H "Authorization: Bearer {api_token}"
DELETE /api/v2/generate/{id}
Auth required. Cancel a pending or processing generation.
Response (200):
{
"data": {
"id": "42",
"status": "failed"
},
"meta": { "api_version": "2.0" }
}
Error (409): Generation already completed or failed.
{
"error": {
"code": "INVALID_STATE",
"message": "Generation cannot be cancelled in current status: completed"
},
"meta": { "api_version": "2.0" }
}
curl -X DELETE https://media.y83.ru/api/v2/generate/42 \
-H "Authorization: Bearer {api_token}"
GET /api/v2/tokens
Auth required. List all personal access tokens.
Response (200):
{
"data": [
{
"id": 1,
"name": "my-integration",
"last_used_at": "2026-08-27T10:30:00.000Z",
"created_at": "2026-08-20T08:00:00.000Z"
}
],
"meta": { "api_version": "2.0" }
}
curl https://media.y83.ru/api/v2/tokens \
-H "Authorization: Bearer {api_token}"
POST /api/v2/tokens
Auth required (Bearer token). Create an additional personal access token.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Token name (max 255 chars) |
Response (201):
{
"data": {
"id": 3,
"name": "my-integration",
"token": "1|abc123def456ghi789...",
"created_at": "2026-08-28T12:00:00.000Z"
},
"meta": { "api_version": "2.0" }
}
Warning: The plain-text token is only shown in this response. Store it securely.
curl -X POST https://media.y83.ru/api/v2/tokens \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
-d '{"name": "my-integration"}'
DELETE /api/v2/tokens/{id}
Auth required. Permanently revoke a token.
Response (200):
{
"data": {
"message": "Token revoked"
},
"meta": { "api_version": "2.0" }
}
curl -X DELETE https://media.y83.ru/api/v2/tokens/3 \
-H "Authorization: Bearer {api_token}"
Fast Flux.2 model. 4B parameters, GGUF quantized. Optimized for speed.
| Parameter | Default | Range |
|---|---|---|
steps |
4 | 1–8 |
cfg |
1.0 | 0–30 |
width |
1024 | 256–2048 |
height |
1024 | 256–2048 |
Features: text2img, img2img (pass image as base64 data URI)
Krea 2 Turbo diffusion transformer. 12B params distilled to 8 steps. Supports LoRAs, style references, and concept sliders.
| Parameter | Default | Range |
|---|---|---|
steps |
8 | 1–9 |
cfg |
1.0 | 0–30 |
width |
1024 | 256–2048 |
height |
1024 | 256–2048 |
megapixels |
1.0 | 0.5–2.0 |
Features: text2img, img2img, inpaint, outpaint, style_references, concept_sliders, loras, post_upscale, 4step
| ID | Name | Trigger Phrase |
|---|---|---|
krea2_darkbrush |
Dark Brush | monochrome ink wash style |
krea2_dotmatrix |
Dot Matrix | Monochrome stippling style |
krea2_kidsdrawing |
Kids Drawing | naive expressive sketch style |
krea2_neondrip |
Neon Drip | Textured abstract style |
krea2_rainywindow |
Rainy Window | rainy window style |
krea2_retroanime |
Retro Anime | Purple retro anime style |
krea2_softwatercolor |
Soft Watercolor | Art Deco watercolor style |
krea2_sunsetblur |
Sunset Blur | ethereal motion blur style |
krea2_vintagetarot |
Vintage Tarot | vintage tarot style |
krea2_enhancer |
Enhancer | universal quality boost |
krea2_realism_v2 |
Realism V2 | photorealism |
krea2_ideokrea |
IdeoKrea | Ideogram 4 style |
The recipe field in style_refs controls how the reference image influences generation:
| Recipe | Description |
|---|---|
balanced |
Balanced mix of all style aspects |
keep the same subject |
Preserve subject, restyle everything |
suggest the visual style |
Apply visual style to new subject |
copy lighting and mood |
Match lighting and atmosphere |
suggest material or texture |
Apply surface/material properties |
avoid copying text/logos |
Style without reproducing text |
suggest the color palette |
Match color scheme |
use the background/setting |
Apply environment/background |
copy the camera framing |
Match composition and framing |
The full recipe list (starter / designer / edit packs — 31 values) is defined in
openapi.yaml (StyleRef.recipe enum). Unknown values are
rejected with 422.
Fine-grained control via named sliders:
{
"concept_sliders": [
{
"name": "blur",
"value": -3.5,
"negative_pole": "sharp focused",
"positive_pole": "blurry dreamy"
}
]
}
Value range: -6 (negative pole) to +6 (positive pole). Max 8 sliders.
When you provide a webhook_url, we POST a payload to that URL when the generation completes or fails.
Note:
webhook_urlmust be a public http/https address. Private, loopback and reserved ranges (localhost,127.0.0.0/8,10.0.0.0/8,192.168.0.0/16, link-local, CGNAT, etc.) are rejected at validation and re-checked right before delivery (SSRF protection).
{
"event": "generation.completed",
"data": {
"id": "42",
"status": "completed",
"model": "krea2-turbo",
"result": {
"images": [
{ "url": "https://media.y83.ru/uploads/gen_abc123.png", "width": 1024, "height": 1024 }
],
"seed": 1234567890,
"generation_time_ms": 8432
}
},
"meta": { "api_version": "2.0" }
}
Events: generation.completed, generation.failed
Each webhook request includes a signature header for verification:
X-Webhook-Signature: sha256={hex_digest}
import hmac
import hashlib
def verify_webhook(payload_body: bytes, signature_header: str, secret: str) -> bool:
"""Verify webhook HMAC-SHA256 signature."""
expected = 'sha256=' + hmac.new(
secret.encode(),
payload_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
const crypto = require('crypto');
function verifyWebhook(payloadBody, signatureHeader, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payloadBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);
}
function verifyWebhook(string $payloadBody, string $signatureHeader, string $secret): bool
{
$expected = 'sha256=' . hash_hmac('sha256', $payloadBody, $secret);
return hash_equals($expected, $signatureHeader);
}
app.post('/webhooks/generation', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'];
if (!verifyWebhook(req.body, signature, 'my_secret_key')) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body);
console.log(`Generation ${event.data.id}: ${event.data.status}`);
// Process result...
res.status(200).json({ received: true });
});
| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR |
422 | Request body failed validation |
UNAUTHENTICATED |
401 | Missing or invalid Bearer token |
FORBIDDEN |
403 | Token lacks required permissions |
NOT_FOUND |
404 | Resource does not exist or belongs to another user |
INVALID_STATE |
409 | Operation not allowed in current state |
RATE_LIMIT_EXCEEDED |
429 | Too many requests (100/min per token; login 5/min per email+IP) |
GENERATION_FAILED |
— | Generation failed (see error message) |
cancelled |
— | Generation was cancelled by user |
{
"error": {
"code": "VALIDATION_ERROR",
"http_status": 422,
"message": "Ошибка валидации",
"details": {
"model": ["Model is required"],
"steps": ["The steps field must not be greater than 8."]
}
},
"meta": { "api_version": "2.0" }
}
Nested fields keep their dotted key, so the index of the offending rectangle is visible:
{
"error": {
"code": "VALIDATION_ERROR",
"http_status": 422,
"message": "Ошибка валидации",
"details": {
"mask_region.0.w": ["mask_region[].w must be an integer from 1 to 2048 (width, source pixels)"],
"anypaint_padding.left": ["anypaint_padding.left must be an integer from 0 to 2048"]
}
},
"meta": { "api_version": "2.0" }
}
Note for clients parsing
detailsas a JSON path: keys likemask_region.0.wcontain dots, so read them as plain map keys rather than walking the tree.
Everything an autonomous agent needs, in order. No session, no CSRF — just a Bearer token.
BASE=https://media.y83.ru/api/v2
# 1. Discover capabilities (public). Includes links to these docs.
curl -s $BASE/health
# 2. Authenticate once, reuse the token
TOKEN=$(curl -s -X POST $BASE/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "agent@example.com", "password": "...", "name": "agent"}' \
| jq -r '.data.token')
# 3. Check what krea2-turbo can do: inpaint contract, recipes, sliders
curl -s $BASE/models | jq '.data["krea2-turbo"] | {features, inpaint, recipes: .recipes[0:3], concept_sliders}'
# 4. Generate a base image. Idempotency-Key makes the call safe to retry:
# a repeat with the same key returns the same job instead of a second one.
JOB=$(curl -s -X POST $BASE/generate \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: quickstart-mug" \
-d '{"model":"krea2-turbo","prompt":"a wooden mug on a kitchen table"}' \
| jq -r '.data.id')
# 5. Wait for it in one call. The job takes ~20-80 s, so repeat the call while
# the status is not terminal. poll_after_ms tells a sane interval if you
# prefer short calls.
RESULT=$(curl -s "$BASE/generate/$JOB?wait=30" -H "Authorization: Bearer $TOKEN")
while echo "$RESULT" | jq -e '.data.status | test("pending|processing|retry_pending")' > /dev/null; do
RESULT=$(curl -s "$BASE/generate/$JOB?wait=30" -H "Authorization: Bearer $TOKEN")
done
URL=$(echo "$RESULT" | jq -r '.data.result.images[0].url')
# 6. Repaint a region of it — coordinates in the image's own pixels
curl -s -X POST $BASE/generate \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d "$(jq -n --arg url "$URL" '{
model: "krea2-turbo",
prompt: "replace the wooden mug with a ceramic teapot",
image_url: $url,
mask_region: [{x: 200, y: 300, w: 600, h: 400}],
denoise: 1.0
}')"
# 7. If you lost a job ID, find it here
curl -s "$BASE/generations?per_page=5" -H "Authorization: Bearer $TOKEN"
Or run steps 4–6 in one shot with the bundled helper:
export Y83_API_TOKEN="$TOKEN"
php scripts/krea-inpaint.php \
--image="$URL" \
--prompt="replace the wooden mug with a ceramic teapot" \
--region=200,300,600,400 \
--denoise=1 \
--out=./result
# 1. Get a token
TOKEN=$(curl -s -X POST https://media.y83.ru/api/v2/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "secret", "name": "cli-tool"}' | jq -r '.data.token')
# 2. Submit generation
RESPONSE=$(curl -s -X POST https://media.y83.ru/api/v2/generate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "flux2-klein-4b",
"prompt": "a serene Japanese garden with koi pond",
"width": 1024,
"height": 768,
"steps": 6
}')
JOB_ID=$(echo $RESPONSE | jq -r '.data.id')
echo "Job ID: $JOB_ID"
# 3. Poll until completed
while true; do
STATUS=$(curl -s https://media.y83.ru/api/v2/generate/$JOB_ID \
-H "Authorization: Bearer $TOKEN")
STATE=$(echo $STATUS | jq -r '.data.status')
echo "Status: $STATE"
if [ "$STATE" = "completed" ]; then
echo $STATUS | jq '.data.result.images[].url'
break
elif [ "$STATE" = "failed" ]; then
echo $STATUS | jq '.data.error'
break
fi
sleep 3
done
# Submit Krea 2 generation with Rainy Window LoRA + upscale
curl -X POST https://media.y83.ru/api/v2/generate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "krea2-turbo",
"prompt": "a cat sitting on a windowsill, rainy city outside",
"krea_lora": "krea2_rainywindow",
"krea_lora_strength": 0.85,
"post_upscale": "2x",
"megapixels": 1.5,
"width": 1024,
"height": 768
}'
# Submit with webhook — no polling needed
curl -X POST https://media.y83.ru/api/v2/generate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "krea2-turbo",
"prompt": "abstract digital art, vibrant colors",
"webhook_url": "https://myapp.com/api/webhooks/y83",
"webhook_secret": "whsec_mysupersecretkey"
}'
# Cancel a generation
curl -X DELETE https://media.y83.ru/api/v2/generate/42 \
-H "Authorization: Bearer $TOKEN"
# If already completed, returns 409:
# {
# "error": {
# "code": "INVALID_STATE",
# "message": "Generation cannot be cancelled in current status: completed"
# }
# }