Driving the lab from your own code
Two model runs sit behind this app and both are reachable over HTTP. The
compose lane writes a six-block image prompt from a plain request; the
render lane turns a finished prompt into a picture with gpt-image.
Everything else the app does — matching a template, checking a prompt's structure, the 541
example prompts — happens in the browser and is not an API call at all.
Base URL and envelope
Every endpoint lives under https://api.skillsafe.ai/v1/app-api and answers with the same envelope.
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "INSUFFICIENT_CREDITS", "message": "..." } }
Errors you will actually meet
| code | what happened | what to do |
|---|---|---|
UNAUTHORIZED | No token, or it expired. | Mint a new one — step 2. |
INSUFFICIENT_CREDITS | Balance below the hold. | Call /estimate first and compare against /me. |
VALIDATION_ERROR | The body was not the input object. | Post the fields at the top level — there is no input wrapper. |
RATE_LIMITED | Too many calls. | Back off; do not tight-loop. |
NOT_FOUND | Unknown job id. | Job ids come from /run and are per-account. |
{"input": {...}} returns 200 and runs anyway — with every one of your
fields hidden from the model, including task. The run looks like it worked and the
answer is generic. Post the fields flat.
The lanes
task selects the lane on a text run. There is exactly one text lane today; the
render lane is selected by the $model override instead and takes no task.
| lane | selected by | model | returns |
|---|---|---|---|
compose | "task": "compose" | gpt-terra → gpt-5.6-terra | one JSON object of prompt blocks |
render | "$model": "gpt-image" | gpt-image | job.output.images[0].b64 |
compose — input fields
| field | type | what it is |
|---|---|---|
task | string | always "compose" |
request | string | what the person wants a picture of, in any language |
guide | string | the writing guide, served verbatim at /compose-prompt.js |
language | string | "en" or "zh" — the language to write the prompt in |
aspect_ratio | string | one of 1:1, 3:2, 2:3, 16:9, 9:16, 4:3, 3:4, or empty to let the writer choose |
template, template_id | string | the matched template's title and id |
template_use_when | string | when that template applies |
template_guidance | string | what this kind of picture needs |
template_pitfalls | string | what goes wrong without a rule |
$files | string[] | up to 4 file ids from POST /files — reference pictures the writer looks at |
reference_brief | string | sent whenever $files is: tells the writer to describe the pictures into the blocks rather than point at them |
reference_count | string | how many pictures are attached |
Reference pictures — the compose lane only
POST /run with
"$model": "gpt-image" plus file ids returns
400 validation_error: "This model generates images — $files attachments are not supported
on image-generation runs". There is no image-in/image-out call. Worse,
/estimate approves that exact body first, so a clean estimate proves nothing here.
So attachments go on the compose run, which is a text model and reads them fine. The writer looks at your picture and describes it into the prompt blocks; the renderer then paints from words alone. Upload first:
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/files" -H "Authorization: Bearer $TOKEN" -F "file=@reference.png" -F "name=reference.png"
# -> {"ok":true,"data":{"file":{"file_id":"udf_...","content_type":"image/png"}}}
then pass the ids as $files on the compose body. File ids are
subject-scoped — an id minted with a guest token 404s once you sign in, so upload
against the subject that will run.
Every value is a scalar. The template fields are joined strings, not arrays — the transport
takes scalars only, and an array arrives at the model as [object Object].
compose — output contract
One JSON object, no prose around it. This is exactly what parseCompose in
render.js reads, and the app assembles blocks into the final
prompt in the order below — the model never writes the assembled string.
{
"title": "French press cutaway explainer",
"language": "en",
"aspect_ratio": "1:1",
"template_id": "infographic-engine",
"blocks": {
"subject": "An explainer diagram of a French press coffee maker, shown in cutaway ...",
"composition": "Square composition, the press centred, four callouts down the left ...",
"style": "Clean editorial infographic, flat vector with real glass transparency ...",
"text": "All labels in English. The title reads \"How a French Press Works\" ...",
"format": "1:1 square, ultra sharp, made to be read at 1024 pixels wide ...",
"constraints": "No brand marks. Do not let leader lines cross or pass through glass ..."
},
"why": "One vague line about a mechanism, so the template forces the cutaway and the labels.",
"variations": [
{
"label": "Blueprint",
"change": "Redraw on a deep navy ground as a white-line blueprint."
},
{
"label": "Step sequence",
"change": "Four presses in a row: grind, pour, steep, press."
}
]
}
A refusal comes back as {"refused": true, "reason": "..."} instead.
render — input and output
Exactly two keys. Every extra key is joined into the text the renderer sees and painted into the picture as literal words, so nothing else belongs here.
{
"instruction": "<<the finished prompt — the six blocks joined by blank lines>>",
"$model": "gpt-image"
}
The payload is job.output.images[0] — {content_type, b64}. On an image
run job.output.output is the empty string, and reading it is the first mistake a
text-lane habit produces.
1 · A tiny client
Unwraps the envelope and raises on the error shape. Everything below assumes it.
# Every call needs a token and returns {"ok":true,"data":{...}} or {"ok":false,"error":{...}}.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="YOUR_TOKEN" # from https://awesome-gpt-image-prompts.skillsafe.ai/tokens.html
# unwrap the envelope with jq
call() { curl -s -X "$1" "$BASE$2" -H "Authorization: Bearer $TOKEN" \
${3:+-H "Content-Type: application/json"} ${3:+-d "$3"} | jq '.data // .error'; }
import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # from https://awesome-gpt-image-prompts.skillsafe.ai/tokens.html
def call(method, path, body=None):
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(BASE + path, data=data, method=method)
req.add_header("Authorization", "Bearer " + TOKEN)
if data:
req.add_header("Content-Type", "application/json")
with urllib.request.urlopen(req) as r:
payload = json.load(r)
if not payload.get("ok"):
raise RuntimeError(payload.get("error"))
return payload["data"]
const BASE = "https://api.skillsafe.ai/v1/app-api";
const TOKEN = "YOUR_TOKEN"; // from https://awesome-gpt-image-prompts.skillsafe.ai/tokens.html
async function call(method, path, body) {
const res = await fetch(BASE + path, {
method,
headers: {
Authorization: `Bearer ${TOKEN}`,
...(body ? { "Content-Type": "application/json" } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const payload = await res.json();
if (!payload.ok) throw new Error(JSON.stringify(payload.error));
return payload.data;
}
package main
import (
"bytes"; "encoding/json"; "fmt"; "io"; "net/http"
)
const base = "https://api.skillsafe.ai/v1/app-api"
const token = "YOUR_TOKEN" // from https://awesome-gpt-image-prompts.skillsafe.ai/tokens.html
func call(method, path string, body any) (map[string]any, error) {
var r io.Reader
if body != nil {
b, _ := json.Marshal(body)
r = bytes.NewReader(b)
}
req, _ := http.NewRequest(method, base+path, r)
req.Header.Set("Authorization", "Bearer "+token)
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
var payload struct {
OK bool `json:"ok"`
Data map[string]any `json:"data"`
Error map[string]any `json:"error"`
}
json.NewDecoder(res.Body).Decode(&payload)
if !payload.OK { return nil, fmt.Errorf("%v", payload.Error) }
return payload.Data, nil
}
import java.net.URI;
import java.net.http.*;
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static final String TOKEN = "YOUR_TOKEN"; // from https://awesome-gpt-image-prompts.skillsafe.ai/tokens.html
static String call(String method, String path, String body) throws Exception {
var b = HttpRequest.newBuilder(URI.create(BASE + path))
.header("Authorization", "Bearer " + TOKEN);
if (body == null) {
b = b.method(method, HttpRequest.BodyPublishers.noBody());
} else {
b = b.header("Content-Type", "application/json")
.method(method, HttpRequest.BodyPublishers.ofString(body));
}
var res = HttpClient.newHttpClient().send(b.build(), HttpResponse.BodyHandlers.ofString());
return res.body(); // {"ok":true,"data":{...}} or {"ok":false,"error":{...}}
}
require "json"
require "net/http"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # from https://awesome-gpt-image-prompts.skillsafe.ai/tokens.html
def call(method, path, body = nil)
uri = URI(BASE + path)
klass = Net::HTTP.const_get(method.capitalize)
req = klass.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
if body
req["Content-Type"] = "application/json"
req.body = JSON.dump(body)
end
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
payload = JSON.parse(res.body)
raise payload["error"].to_s unless payload["ok"]
payload["data"]
end
<?php
$BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = "YOUR_TOKEN"; // from https://awesome-gpt-image-prompts.skillsafe.ai/tokens.html
function call($method, $path, $body = null) {
global $BASE, $TOKEN;
$headers = ["Authorization: Bearer $TOKEN"];
if ($body !== null) $headers[] = "Content-Type: application/json";
$ch = curl_init($BASE . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $body === null ? null : json_encode($body),
]);
$payload = json_decode(curl_exec($ch), true);
curl_close($ch);
if (empty($payload["ok"])) throw new Exception(json_encode($payload["error"]));
return $payload["data"];
}
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
const string Base = "https://api.skillsafe.ai/v1/app-api";
const string Token = "YOUR_TOKEN"; // from https://awesome-gpt-image-prompts.skillsafe.ai/tokens.html
static readonly HttpClient Http = new HttpClient();
static async Task<string> Call(string method, string path, string body) {
var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Token);
if (body != null)
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
var res = await Http.SendAsync(req);
return await res.Content.ReadAsStringAsync();
}
2 · A token
Open tokens.html in the browser, sign in, and copy the token — that is the whole of it. A personal token carries your credit balance; a guest token carries none, so a guest can read prices but not run either lane.
3 · Who am I
Returns subject_type, subject_id and credits — and nothing
else. Signed in means subject_type == "user"; a guest token also resolves here, so
"the call succeeded" is not a sign-in test.
curl -s -X GET "https://api.skillsafe.ai/v1/app-api/me" \
-H "Authorization: Bearer $TOKEN"
print(call("GET", "/me"))
console.log(await call("GET", "/me"));
out, err := call("GET", "/me", nil)
fmt.Println(out, err)
System.out.println(call("GET", "/me", null));
puts call("GET", "/me")
print_r(call("GET", "/me"));
Console.WriteLine(await Call("GET", "/me", null));
4 · What will it cost
Free, and it starts no job. hold_credits is what gets reserved;
charged_credits on the finished job is what you actually pay, usually far less
because the hold prices the full output cap. Assert model_alias here — it is the
authoritative proof you are wired to the model you think you are.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/estimate" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"task": "compose", "request": "explain how a french press works", "language": "en", "aspect_ratio": "1:1", "template": "Infographic Engine", "template_id": "infographic-engine", "template_use_when": "Use for explainers, knowledge maps and structured diagrams.", "template_guidance": "Lock the hierarchy and the exact label text. Say how many panels.", "template_pitfalls": "Avoid decorative charts that carry no data. Constrain label legibility.", "guide": "<<the full text served at /compose-prompt.js>>"}'
print(call("POST", "/estimate", {
"task": "compose",
"request": "explain how a french press works",
"language": "en",
"aspect_ratio": "1:1",
"template": "Infographic Engine",
"template_id": "infographic-engine",
"template_use_when": "Use for explainers, knowledge maps and structured diagrams.",
"template_guidance": "Lock the hierarchy and the exact label text. Say how many panels.",
"template_pitfalls": "Avoid decorative charts that carry no data. Constrain label legibility.",
"guide": "<<the full text served at /compose-prompt.js>>"
}))
console.log(await call("POST", "/estimate", {
"task": "compose",
"request": "explain how a french press works",
"language": "en",
"aspect_ratio": "1:1",
"template": "Infographic Engine",
"template_id": "infographic-engine",
"template_use_when": "Use for explainers, knowledge maps and structured diagrams.",
"template_guidance": "Lock the hierarchy and the exact label text. Say how many panels.",
"template_pitfalls": "Avoid decorative charts that carry no data. Constrain label legibility.",
"guide": "<<the full text served at /compose-prompt.js>>"
}));
body := map[string]any{}
json.Unmarshal([]byte(`{"task": "compose", "request": "explain how a french press works", "language": "en", "aspect_ratio": "1:1", "template": "Infographic Engine", "template_id": "infographic-engine", "template_use_when": "Use for explainers, knowledge maps and structured diagrams.", "template_guidance": "Lock the hierarchy and the exact label text. Say how many panels.", "template_pitfalls": "Avoid decorative charts that carry no data. Constrain label legibility.", "guide": "<<the full text served at /compose-prompt.js>>"}`), &body)
out, err := call("POST", "/estimate", body)
fmt.Println(out, err)
String body = """
{"task": "compose", "request": "explain how a french press works", "language": "en", "aspect_ratio": "1:1", "template": "Infographic Engine", "template_id": "infographic-engine", "template_use_when": "Use for explainers, knowledge maps and structured diagrams.", "template_guidance": "Lock the hierarchy and the exact label text. Say how many panels.", "template_pitfalls": "Avoid decorative charts that carry no data. Constrain label legibility.", "guide": "<<the full text served at /compose-prompt.js>>"}
""";
System.out.println(call("POST", "/estimate", body));
puts call("POST", "/estimate", {
"task": "compose",
"request": "explain how a french press works",
"language": "en",
"aspect_ratio": "1:1",
"template": "Infographic Engine",
"template_id": "infographic-engine",
"template_use_when": "Use for explainers, knowledge maps and structured diagrams.",
"template_guidance": "Lock the hierarchy and the exact label text. Say how many panels.",
"template_pitfalls": "Avoid decorative charts that carry no data. Constrain label legibility.",
"guide": "<<the full text served at /compose-prompt.js>>"
})
print_r(call("POST", "/estimate", json_decode(<<<'J'
{"task": "compose", "request": "explain how a french press works", "language": "en", "aspect_ratio": "1:1", "template": "Infographic Engine", "template_id": "infographic-engine", "template_use_when": "Use for explainers, knowledge maps and structured diagrams.", "template_guidance": "Lock the hierarchy and the exact label text. Say how many panels.", "template_pitfalls": "Avoid decorative charts that carry no data. Constrain label legibility.", "guide": "<<the full text served at /compose-prompt.js>>"}
J, true)));
var body = @"{""task"": ""compose"", ""request"": ""explain how a french press works"", ""language"": ""en"", ""aspect_ratio"": ""1:1"", ""template"": ""Infographic Engine"", ""template_id"": ""infographic-engine"", ""template_use_when"": ""Use for explainers, knowledge maps and structured diagrams."", ""template_guidance"": ""Lock the hierarchy and the exact label text. Say how many panels."", ""template_pitfalls"": ""Avoid decorative charts that carry no data. Constrain label legibility."", ""guide"": ""<<the full text served at /compose-prompt.js>>""}";
Console.WriteLine(await Call("POST", "/estimate", body));
An image run is priced per picture and its hold does not move with prompt length, so one probe covers every prompt you will ever send. 1 credit = $0.0001.
5 · Compose a prompt
/run returns a job_id; poll /jobs/{id} until
status is succeeded or failed. Send an
Idempotency-Key header if you retry — but salt it per attempt, because
an idempotent replay returns the original job even when that job failed.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/run" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"task": "compose", "request": "explain how a french press works", "language": "en", "aspect_ratio": "1:1", "template": "Infographic Engine", "template_id": "infographic-engine", "template_use_when": "Use for explainers, knowledge maps and structured diagrams.", "template_guidance": "Lock the hierarchy and the exact label text. Say how many panels.", "template_pitfalls": "Avoid decorative charts that carry no data. Constrain label legibility.", "guide": "<<the full text served at /compose-prompt.js>>"}'
print(call("POST", "/run", {
"task": "compose",
"request": "explain how a french press works",
"language": "en",
"aspect_ratio": "1:1",
"template": "Infographic Engine",
"template_id": "infographic-engine",
"template_use_when": "Use for explainers, knowledge maps and structured diagrams.",
"template_guidance": "Lock the hierarchy and the exact label text. Say how many panels.",
"template_pitfalls": "Avoid decorative charts that carry no data. Constrain label legibility.",
"guide": "<<the full text served at /compose-prompt.js>>"
}))
console.log(await call("POST", "/run", {
"task": "compose",
"request": "explain how a french press works",
"language": "en",
"aspect_ratio": "1:1",
"template": "Infographic Engine",
"template_id": "infographic-engine",
"template_use_when": "Use for explainers, knowledge maps and structured diagrams.",
"template_guidance": "Lock the hierarchy and the exact label text. Say how many panels.",
"template_pitfalls": "Avoid decorative charts that carry no data. Constrain label legibility.",
"guide": "<<the full text served at /compose-prompt.js>>"
}));
body := map[string]any{}
json.Unmarshal([]byte(`{"task": "compose", "request": "explain how a french press works", "language": "en", "aspect_ratio": "1:1", "template": "Infographic Engine", "template_id": "infographic-engine", "template_use_when": "Use for explainers, knowledge maps and structured diagrams.", "template_guidance": "Lock the hierarchy and the exact label text. Say how many panels.", "template_pitfalls": "Avoid decorative charts that carry no data. Constrain label legibility.", "guide": "<<the full text served at /compose-prompt.js>>"}`), &body)
out, err := call("POST", "/run", body)
fmt.Println(out, err)
String body = """
{"task": "compose", "request": "explain how a french press works", "language": "en", "aspect_ratio": "1:1", "template": "Infographic Engine", "template_id": "infographic-engine", "template_use_when": "Use for explainers, knowledge maps and structured diagrams.", "template_guidance": "Lock the hierarchy and the exact label text. Say how many panels.", "template_pitfalls": "Avoid decorative charts that carry no data. Constrain label legibility.", "guide": "<<the full text served at /compose-prompt.js>>"}
""";
System.out.println(call("POST", "/run", body));
puts call("POST", "/run", {
"task": "compose",
"request": "explain how a french press works",
"language": "en",
"aspect_ratio": "1:1",
"template": "Infographic Engine",
"template_id": "infographic-engine",
"template_use_when": "Use for explainers, knowledge maps and structured diagrams.",
"template_guidance": "Lock the hierarchy and the exact label text. Say how many panels.",
"template_pitfalls": "Avoid decorative charts that carry no data. Constrain label legibility.",
"guide": "<<the full text served at /compose-prompt.js>>"
})
print_r(call("POST", "/run", json_decode(<<<'J'
{"task": "compose", "request": "explain how a french press works", "language": "en", "aspect_ratio": "1:1", "template": "Infographic Engine", "template_id": "infographic-engine", "template_use_when": "Use for explainers, knowledge maps and structured diagrams.", "template_guidance": "Lock the hierarchy and the exact label text. Say how many panels.", "template_pitfalls": "Avoid decorative charts that carry no data. Constrain label legibility.", "guide": "<<the full text served at /compose-prompt.js>>"}
J, true)));
var body = @"{""task"": ""compose"", ""request"": ""explain how a french press works"", ""language"": ""en"", ""aspect_ratio"": ""1:1"", ""template"": ""Infographic Engine"", ""template_id"": ""infographic-engine"", ""template_use_when"": ""Use for explainers, knowledge maps and structured diagrams."", ""template_guidance"": ""Lock the hierarchy and the exact label text. Say how many panels."", ""template_pitfalls"": ""Avoid decorative charts that carry no data. Constrain label legibility."", ""guide"": ""<<the full text served at /compose-prompt.js>>""}";
Console.WriteLine(await Call("POST", "/run", body));
Then poll:
curl -s -X GET "https://api.skillsafe.ai/v1/app-api/jobs/job_123" \
-H "Authorization: Bearer $TOKEN"
print(call("GET", "/jobs/job_123"))
console.log(await call("GET", "/jobs/job_123"));
out, err := call("GET", "/jobs/job_123", nil)
fmt.Println(out, err)
System.out.println(call("GET", "/jobs/job_123", null));
puts call("GET", "/jobs/job_123")
print_r(call("GET", "/jobs/job_123"));
Console.WriteLine(await Call("GET", "/jobs/job_123", null));
6 · Render the prompt
Same /run endpoint, different body. Takes up to three minutes; poll at a couple of
seconds. A failed job carries error as a plain string about as often as an object,
so read both shapes.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/run" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"instruction": "<<the finished prompt — the six blocks joined by blank lines>>", "$model": "gpt-image"}'
print(call("POST", "/run", {
"instruction": "<<the finished prompt — the six blocks joined by blank lines>>",
"$model": "gpt-image"
}))
console.log(await call("POST", "/run", {
"instruction": "<<the finished prompt — the six blocks joined by blank lines>>",
"$model": "gpt-image"
}));
body := map[string]any{}
json.Unmarshal([]byte(`{"instruction": "<<the finished prompt — the six blocks joined by blank lines>>", "$model": "gpt-image"}`), &body)
out, err := call("POST", "/run", body)
fmt.Println(out, err)
String body = """
{"instruction": "<<the finished prompt — the six blocks joined by blank lines>>", "$model": "gpt-image"}
""";
System.out.println(call("POST", "/run", body));
puts call("POST", "/run", {
"instruction": "<<the finished prompt — the six blocks joined by blank lines>>",
"$model": "gpt-image"
})
print_r(call("POST", "/run", json_decode(<<<'J'
{"instruction": "<<the finished prompt — the six blocks joined by blank lines>>", "$model": "gpt-image"}
J, true)));
var body = @"{""instruction"": ""<<the finished prompt — the six blocks joined by blank lines>>"", ""$model"": ""gpt-image""}";
Console.WriteLine(await Call("POST", "/run", body));
7 · Streaming
# Streaming is for the COMPOSE lane only. An image run sends no deltas — use /run and poll.
curl -N -X POST "https://api.skillsafe.ai/v1/app-api/run-stream" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"task": "compose", "request": "explain how a french press works", "language": "en", "aspect_ratio": "1:1", "template": "Infographic Engine", "template_id": "infographic-engine", "template_use_when": "Use for explainers, knowledge maps and structured diagrams.", "template_guidance": "Lock the hierarchy and the exact label text. Say how many panels.", "template_pitfalls": "Avoid decorative charts that carry no data. Constrain label legibility.", "guide": "<<the full text served at /compose-prompt.js>>"}'
# Streaming is for the COMPOSE lane only. An image run sends no deltas — use /run and poll.
import requests
with requests.post(BASE + "/run-stream", headers={"Authorization": "Bearer " + TOKEN},
json=COMPOSE_BODY, stream=True) as r:
for line in r.iter_lines():
if line.startswith(b"data: "):
print(line[6:].decode())
// Streaming is for the COMPOSE lane only. An image run sends no deltas — use /run and poll.
const res = await fetch(BASE + "/run-stream", {
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify(COMPOSE_BODY),
});
const reader = res.body.getReader();
const dec = new TextDecoder();
for (;;) {
const { value, done } = await reader.read();
if (done) break;
process.stdout.write(dec.decode(value, { stream: true }));
}
// Streaming is for the COMPOSE lane only. An image run sends no deltas — use /run and poll.
req, _ := http.NewRequest("POST", base+"/run-stream", bytes.NewReader(payload))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
sc := bufio.NewScanner(res.Body)
for sc.Scan() {
if line := sc.Text(); strings.HasPrefix(line, "data: ") {
fmt.Println(line[6:])
}
}
// Streaming is for the COMPOSE lane only. An image run sends no deltas — use /run and poll.
var req = HttpRequest.newBuilder(URI.create(BASE + "/run-stream"))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(composeBody))
.build();
HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofLines())
.body()
.filter(l -> l.startsWith("data: "))
.forEach(l -> System.out.println(l.substring(6)));
# Streaming is for the COMPOSE lane only. An image run sends no deltas — use /run and poll.
uri = URI(BASE + "/run-stream")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req.body = JSON.dump(COMPOSE_BODY)
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(req) do |res|
res.read_body { |chunk| print chunk }
end
end
<?php
// Streaming is for the COMPOSE lane only. An image run sends no deltas — use /run and poll.
$ch = curl_init("$BASE/run-stream");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $TOKEN", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode($composeBody),
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { echo $chunk; return strlen($chunk); },
]);
curl_exec($ch);
curl_close($ch);
// Streaming is for the COMPOSE lane only. An image run sends no deltas — use /run and poll.
var req = new HttpRequestMessage(HttpMethod.Post, Base + "/run-stream");
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Token);
req.Content = new StringContent(composeBody, Encoding.UTF8, "application/json");
var res = await Http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
using var reader = new StreamReader(await res.Content.ReadAsStreamAsync());
string line;
while ((line = await reader.ReadLineAsync()) != null)
if (line.StartsWith("data: ")) Console.WriteLine(line[6..]);
Templates and the example corpus come from awesome-gpt-image-2 (MIT). Back to the lab.