Impossible Creature Generator draws invented animals as plates from a field guide to a world that does not exist. In the browser you move through an eight-axis space — body plan, habitat, adult mass, feeding guild, locomotion, body covering, thermal strategy and plate style — and twenty-two deterministic rules check the combination against real allometry before anything is rendered. The app then compiles those choices, and the anatomy they force, into a single block of prose which it hands to an image model. The web app is a thin front end over a public HTTPS API. Everything it does you can do from a script, and this page is the whole contract.
The coherence engine is the part worth reimplementing rather than calling. It is pure client-side arithmetic over mass, medium, temperature and skeleton type, it costs nothing, and it is what stops you paying to render a 320 kg animal with functional wings.
Base URL:
https://api.skillsafe.ai/v1/app-api
Authentication is one header:
Authorization: Bearer YOUR_TOKEN
The token is app-scoped: it already says which app you are calling, so there is no slug header and no slug in the path. Get one from tokens.html, or mint a guest token with the call in step 1.
The envelope
Every response body, at every status code, is the same two-branch envelope. Read
data. Never read fields off the top level.
{"ok": true, "data": { ... }}
{"ok": false, "error": {"code": "...", "message": "...", "details": { ... }}}
Check ok before you touch anything else. A refused run and a broken
connection do not look alike: the first is a well-formed envelope with ok: false,
the second is not JSON at all. The helper in step 2 handles both.
Endpoints
| Method and path | Costs credits | What it is for |
|---|---|---|
POST /guest | No | Mint an anonymous token. No Authorization header on this one. |
GET /me | No | Who the token belongs to, and the balance. |
POST /estimate | No | The hold a run of this shape would reserve. |
POST /run | Yes | Starts a job and returns job_id immediately. |
GET /jobs/{job_id} | No | Poll a job to a terminal state. |
POST /run-stream also exists on the platform. It is a text lane:
it streams token deltas over server-sent events, which is exactly nothing when the output is a
PNG. An image run gives you no partial picture and no progress deltas, so streaming buys you
no earlier information than polling does. Impossible Creature Generator is /run plus
GET /jobs/{id}, and this page documents no SSE.
The body is two keys
This is the single most important thing on the page, so it goes before the tutorial rather than inside it. A Impossible Creature Generator run body has exactly two keys:
{"instruction": "<the whole compiled plate brief, as plain prose>", "$model": "gpt-image"}
There is no task field. There is no style field. There is no
count field. There is no field for the eight axes, no field for aspect ratio and
no field for a seed. The axes are not parameters; they are sentences you have already written
into instruction.
Every additional key is concatenated into the text the image model sees, and the
image model paints the text it is given. This is why a stray key is not a harmless
no-op and not a 400. The API accepts the body, reserves the hold, runs the job
and returns 200 OK with a picture that has the words task creature
lettered across the plate beside your animal. You paid for it, and it is ruined — and
on a field-guide plate, which already invites handwritten labels, it is the single easiest
way to waste a run.
Wrong — four keys, and three of them become graffiti:
{"task": "creature",
"instruction": "A single plate from a naturalist's field guide to a world that does not exist ...",
"style": "hand-coloured lithograph",
"count": 1,
"$model": "gpt-image"}
Right — the style and the count live in the prose, or they do not exist:
{"instruction": "A single plate from a naturalist's field guide to a world that does not exist ... The plate is a hand-coloured lithograph, fine stone-drawn outline with transparent watercolour laid over it by hand.",
"$model": "gpt-image"}
$model is what makes it an image run at all. Drop it and the
request goes to the app's text model, which will cheerfully write you three paragraphs
describing the painting you asked for. It also changes how the run is priced: per picture
rather than per token. Keep it, spell it exactly gpt-image, and note that
$model is not a legal identifier in Go, C# or Java, so those languages build the
body as a map or a string rather than a struct.
One run is one picture. There is no batch parameter, because there is no
parameter surface at all. Four pictures are four calls to /run, four holds and
four jobs to poll.
# Build the body in a file so the shell never has to quote the brief.
# BRIEF is the compiled plate brief from step 6.
cat > body.json <<'JSON'
{"instruction": "A single plate from a naturalist's field guide to a world that does not exist ...", "$model": "gpt-image"}
JSON
# Two keys. Confirm it before you spend anything on it:
jq -e 'keys == ["$model", "instruction"]' body.json >/dev/null \
&& echo "two keys, good" \
|| { echo "extra keys will be painted into the picture" >&2; exit 1; }
# BRIEF is the compiled plate brief from step 6.
BODY = {
"instruction": BRIEF,
"$model": "gpt-image",
}
# Two keys. Anything else here is text the model paints into the frame.
assert set(BODY) == {"instruction", "$model"}, "extra keys become graffiti"
// BRIEF is the compiled plate brief from step 6.
const BODY = {
instruction: BRIEF,
$model: "gpt-image",
};
// Two keys. Anything else here is text the model paints into the frame.
if (Object.keys(BODY).length !== 2) throw new Error("extra keys become graffiti");
// Brief is the compiled plate brief from step 6.
// "$model" is not a legal Go field name, so the body is a map, not a struct.
body := map[string]any{
"instruction": Brief,
"$model": "gpt-image",
}
// Two keys. Anything else here is text the model paints into the frame.
if len(body) != 2 {
log.Fatal("extra keys become graffiti")
}
// BRIEF is the compiled plate brief from step 6.
// The JDK ships no JSON writer, so the body is assembled as a string here; with
// Jackson or Gson build a Map and keep the two key names exactly as shown.
String body = "{\"instruction\": " + ImpossibleCreatureGenerator.jsonString(BRIEF)
+ ", \"$model\": \"gpt-image\"}";
// Two keys. Anything else in there is text the model paints into the frame.
# BRIEF is the compiled plate brief from step 6.
# Single-quote the '$model' key so nothing tries to interpolate it.
BODY = {
'instruction' => BRIEF,
'$model' => 'gpt-image'
}
# Two keys. Anything else here is text the model paints into the frame.
raise 'extra keys become graffiti' unless BODY.keys.sort == ['$model', 'instruction']
<?php
// $BRIEF is the compiled plate brief from step 6.
// Single-quote the '$model' key. Inside a double-quoted string PHP would try to
// interpolate a variable named $model and send you an empty key instead.
$body = [
'instruction' => $BRIEF,
'$model' => 'gpt-image',
];
// Two keys. Anything else here is text the model paints into the frame.
if (count($body) !== 2) {
throw new RuntimeException('extra keys become graffiti');
}
// Brief is the compiled plate brief from step 6.
// "$model" is not a legal C# member name, so use a dictionary rather than an
// anonymous object.
var body = new Dictionary<string, object> {
["instruction"] = Brief,
["$model"] = "gpt-image",
};
// Two keys. Anything else here is text the model paints into the frame.
if (body.Count != 2) throw new InvalidOperationException("extra keys become graffiti");
1. Get a token
Two ways in, and which one you want depends on whose credits are paying.
Your own token. Open tokens.html on this site, sign in, and copy what it shows you:
app token aut_…
That token is scoped to Impossible Creature Generator and needs no companion header. Send it and you are calling this app.
A guest token. POST /guest is the only route on the API that
takes no Authorization header, and the only one that takes a slug. Post
{"slug": "impossible-creature-generator"} and the response carries a fresh
token and a guest_id. A guest is an anonymous subject with the
publisher's sponsored allowance rather than an account, so it can run out mid-script and
cannot be topped up. It is the right choice for a demo and the wrong one for a batch.
A token can spend the credits of whoever minted it. Treat it as a password with a billing relationship attached: a secret manager, a keychain, or your CI provider's encrypted variables. Not a git repository, not a container image, not a log line, and never a front-end bundle — shipping it to a browser publishes it to everyone who opens the page. If one leaks, mint a fresh one from tokens.html; that is the whole remediation.
Every snippet below uses a constant named TOKEN holding the
literal YOUR_TOKEN, so the code reads clearly. In anything you deploy, replace
that literal with a lookup against your secret store.
# A. Your own token, copied from https://impossible-creature-generator.skillsafe.ai/tokens.html
# The leading space keeps it out of shell history in most shells.
SF_TOKEN="YOUR_TOKEN"
# Or prompt for it, so it never lands in a file at all:
# read -rs -p 'Impossible Creature Generator token: ' SF_TOKEN; echo
# B. Or mint a guest token. This is the one call with no Authorization header.
SF_TOKEN=$(curl -sS -X POST "https://api.skillsafe.ai/v1/app-api/guest" \
-H "Content-Type: application/json" \
-d '{"slug": "impossible-creature-generator"}' | jq -r '.data.token')
case "$SF_TOKEN" in
aut_*) echo "ok: app-scoped token" ;;
*) echo "not an app token - copy one from /tokens.html" >&2; exit 1 ;;
esac
import requests
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # from https://impossible-creature-generator.skillsafe.ai/tokens.html
def load_token():
"""Return the app token.
Swap the constant for a real lookup: AWS Secrets Manager, Vault, Google
Secret Manager, the 1Password CLI, your CI provider's encrypted variables -
anything that is not a checked-in file.
"""
if not TOKEN.startswith("aut_"):
raise SystemExit("not an app-scoped token: copy one from /tokens.html")
return TOKEN
def guest_token():
"""Mint an anonymous token. The only call that sends no Authorization header."""
res = requests.post(BASE + "/guest", json={"slug": "impossible-creature-generator"}, timeout=30)
env = res.json()
if not env.get("ok"):
raise SystemExit(env["error"]["message"])
return env["data"]["token"] # env["data"]["guest_id"] is also there
print("token loaded, length", len(load_token()))
const BASE = "https://api.skillsafe.ai/v1/app-api";
const TOKEN = "YOUR_TOKEN"; // from https://impossible-creature-generator.skillsafe.ai/tokens.html
/**
* Returns the app token. Replace the constant with a call into your secret
* store. Never bundle this into anything a browser downloads: a token in a
* front-end build is a token you have published.
*/
function loadToken() {
if (!TOKEN.startsWith("aut_")) {
throw new Error("not an app-scoped token: copy one from /tokens.html");
}
return TOKEN;
}
/** Mints an anonymous token. The only call that sends no Authorization header. */
async function guestToken() {
const res = await fetch(`${BASE}/guest`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ slug: "impossible-creature-generator" }),
});
const env = await res.json();
if (!env.ok) throw new Error(env.error.message);
return env.data.token; // env.data.guest_id is also there
}
console.log("token loaded, length", loadToken().length);
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
)
const Base = "https://api.skillsafe.ai/v1/app-api"
// loadToken returns the app token. Replace the literal with a read from your
// secret store - Vault, Secrets Manager, a mounted file with 0400 on it.
// os.Getenv("SURREAL_FORGE_TOKEN") is fine for a throwaway script.
func loadToken() string {
token := "YOUR_TOKEN" // from https://impossible-creature-generator.skillsafe.ai/tokens.html
if v := os.Getenv("SURREAL_FORGE_TOKEN"); v != "" {
token = v
}
if !strings.HasPrefix(token, "aut_") {
log.Fatal("not an app-scoped token: copy one from /tokens.html")
}
return token
}
// guestToken mints an anonymous token. The only call with no Authorization header.
func guestToken() (string, error) {
payload, _ := json.Marshal(map[string]string{"slug": "impossible-creature-generator"})
res, err := http.Post(Base+"/guest", "application/json", bytes.NewReader(payload))
if err != nil {
return "", err
}
defer res.Body.Close()
var env struct {
OK bool `json:"ok"`
Data struct {
Token string `json:"token"`
GuestID string `json:"guest_id"`
} `json:"data"`
}
if err := json.NewDecoder(res.Body).Decode(&env); err != nil || !env.OK {
return "", fmt.Errorf("guest mint failed: HTTP %d", res.StatusCode)
}
return env.Data.Token, nil
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Tokens {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static final String TOKEN = "YOUR_TOKEN"; // from /tokens.html
/**
* Returns the app token. Replace the constant with your secret manager's
* client. Do not put it in application.properties and do not log it.
*/
static String loadToken() {
if (!TOKEN.startsWith("aut_")) {
throw new IllegalStateException("not an app-scoped token: copy one from /tokens.html");
}
return TOKEN;
}
/** Mints an anonymous token. The only call that sends no Authorization header. */
static String guestToken() throws Exception {
HttpRequest req = HttpRequest.newBuilder(URI.create(BASE + "/guest"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"slug\": \"impossible-creature-generator\"}"))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
// Parse with Jackson or Gson: data.token, and data.guest_id alongside it.
return res.body();
}
public static void main(String[] args) {
System.out.println("token loaded, length " + loadToken().length());
}
}
require 'json'
require 'net/http'
require 'uri'
BASE = 'https://api.skillsafe.ai/v1/app-api'
TOKEN = 'YOUR_TOKEN' # from https://impossible-creature-generator.skillsafe.ai/tokens.html
# Returns the app token. Replace the constant with Rails credentials, a Vault
# client, or whatever your deploy already uses for database passwords.
def load_token
abort 'not an app-scoped token: copy one from /tokens.html' unless TOKEN.start_with?('aut_')
TOKEN
end
# Mints an anonymous token. The only call that sends no Authorization header.
def guest_token
uri = URI("#{BASE}/guest")
res = Net::HTTP.post(uri, JSON.dump({ 'slug' => 'impossible-creature-generator' }),
'Content-Type' => 'application/json')
env = JSON.parse(res.body)
raise env['error']['message'] unless env['ok']
env['data']['token'] # env['data']['guest_id'] is also there
end
puts "token loaded, length #{load_token.length}"
<?php
$BASE = 'https://api.skillsafe.ai/v1/app-api';
$TOKEN = 'YOUR_TOKEN'; // from https://impossible-creature-generator.skillsafe.ai/tokens.html
/**
* Returns the app token. Replace the constant with your secret store's client.
* Keep it out of the document root and out of version control.
*/
function load_token(): string {
global $TOKEN;
if (!str_starts_with($TOKEN, 'aut_')) {
throw new RuntimeException('not an app-scoped token: copy one from /tokens.html');
}
return $TOKEN;
}
/** Mints an anonymous token. The only call that sends no Authorization header. */
function guest_token(): string {
global $BASE;
$ch = curl_init($BASE . '/guest');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['slug' => 'impossible-creature-generator']),
CURLOPT_RETURNTRANSFER => true,
]);
$env = json_decode((string) curl_exec($ch), true);
curl_close($ch);
if (empty($env['ok'])) {
throw new RuntimeException('guest mint failed');
}
return $env['data']['token']; // $env['data']['guest_id'] is also there
}
echo 'token loaded, length ' . strlen(load_token()) . PHP_EOL;
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
const string BaseUrl = "https://api.skillsafe.ai/v1/app-api";
const string Token = "YOUR_TOKEN"; // from https://impossible-creature-generator.skillsafe.ai/tokens.html
// Returns the app token. Replace the constant with IConfiguration backed by
// Azure Key Vault, AWS Secrets Manager, or the .NET user-secrets store.
static string LoadToken()
{
if (!Token.StartsWith("aut_", StringComparison.Ordinal))
{
throw new InvalidOperationException("not an app-scoped token: copy one from /tokens.html");
}
return Token;
}
// Mints an anonymous token. The only call that sends no Authorization header.
static async Task<string> GuestToken()
{
using var http = new HttpClient();
var content = new StringContent("{\"slug\": \"impossible-creature-generator\"}", Encoding.UTF8, "application/json");
using var res = await http.PostAsync(BaseUrl + "/guest", content);
using var doc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
var data = doc.RootElement.GetProperty("data");
return data.GetProperty("token").GetString(); // data.guest_id is also there
}
Console.WriteLine("token loaded, length " + LoadToken().Length);
2. A tiny client
Three things repeat on every call: the base URL, the bearer header, and unwrapping the
envelope. Write them once. The helper below takes a method and a path, sends JSON, raises when
ok is false, and returns data. Every later step assumes it exists and
calls it call.
Give the error object a real home while you are at it. error.code is the field
you branch on, error.message is for humans, and error.details carries
the specifics — such as the min_credits you fell short of.
Set a generous read timeout. A picture takes tens of seconds, and although
/run itself returns at once, a client that gives up after five seconds will
abandon jobs you have already paid for.
# Save as sf.sh and source it. Every later snippet uses sf_call.
SF_TOKEN="YOUR_TOKEN"
SF_BASE="https://api.skillsafe.ai/v1/app-api"
sf_call() { # sf_call METHOD PATH [BODY_FILE]
method="$1"; path="$2"; body_file="$3"
if [ -n "$body_file" ]; then
curl -sS -X "$method" "$SF_BASE$path" \
-H "Authorization: Bearer $SF_TOKEN" \
-H "Content-Type: application/json" \
--data-binary "@$body_file"
else
curl -sS -X "$method" "$SF_BASE$path" \
-H "Authorization: Bearer $SF_TOKEN"
fi
}
# Unwrap with jq. .ok is the gate; everything you want is under .data.
sf_data() { jq -e 'if .ok then .data else error("app-api: " + .error.code + ": " + .error.message) end'; }
sf_call GET /me | sf_data
import base64
import time
import requests
TOKEN = "YOUR_TOKEN" # from https://impossible-creature-generator.skillsafe.ai/tokens.html
BASE = "https://api.skillsafe.ai/v1/app-api"
class AppError(RuntimeError):
def __init__(self, err):
self.code = err.get("code", "unknown")
self.details = err.get("details") or {}
super().__init__("{0}: {1}".format(self.code, err.get("message", "")))
def call(method, path, body=None, headers=None):
"""Call the app API and return the unwrapped `data` object."""
hdrs = {"Authorization": "Bearer " + TOKEN, "Content-Type": "application/json"}
if headers:
hdrs.update(headers)
res = requests.request(method, BASE + path, json=body, headers=hdrs, timeout=300)
try:
env = res.json()
except ValueError:
raise AppError({"code": "transport",
"message": "HTTP %d: %s" % (res.status_code, res.text[:200])})
if not env.get("ok"):
raise AppError(env.get("error") or {"code": "unknown", "message": res.text[:200]})
return env["data"]
const TOKEN = "YOUR_TOKEN"; // from https://impossible-creature-generator.skillsafe.ai/tokens.html
const BASE = "https://api.skillsafe.ai/v1/app-api";
class AppError extends Error {
constructor(err) {
super(`${err.code}: ${err.message}`);
this.name = "AppError";
this.code = err.code;
this.details = err.details || {};
}
}
/** Calls the app API and resolves with the unwrapped `data` object. */
async function call(method, path, body, headers = {}) {
const res = await fetch(BASE + path, {
method,
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
...headers,
},
body: body === undefined ? undefined : JSON.stringify(body),
});
const text = await res.text();
let env;
try {
env = JSON.parse(text);
} catch {
throw new AppError({ code: "transport", message: `HTTP ${res.status}: ${text.slice(0, 200)}` });
}
if (!env.ok) throw new AppError(env.error || { code: "unknown", message: text.slice(0, 200) });
return env.data;
}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
)
const (
Token = "YOUR_TOKEN" // from https://impossible-creature-generator.skillsafe.ai/tokens.html
Base = "https://api.skillsafe.ai/v1/app-api"
)
var client = &http.Client{Timeout: 300 * time.Second}
type apiError struct {
Code string `json:"code"`
Message string `json:"message"`
Details json.RawMessage `json:"details"`
}
func (e *apiError) Error() string { return e.Code + ": " + e.Message }
type envelope struct {
OK bool `json:"ok"`
Data json.RawMessage `json:"data"`
Error *apiError `json:"error"`
}
// call sends JSON and returns the raw `data` object for the caller to unmarshal.
func call(method, path string, body any, extra map[string]string) (json.RawMessage, error) {
var payload []byte
if body != nil {
var err error
if payload, err = json.Marshal(body); err != nil {
return nil, err
}
}
req, err := http.NewRequest(method, Base+path, bytes.NewReader(payload))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+Token)
req.Header.Set("Content-Type", "application/json")
for k, v := range extra {
req.Header.Set(k, v)
}
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
var env envelope
if err := json.NewDecoder(res.Body).Decode(&env); err != nil {
return nil, fmt.Errorf("transport: HTTP %d: %w", res.StatusCode, err)
}
if !env.OK {
if env.Error == nil {
return nil, fmt.Errorf("unknown: HTTP %d", res.StatusCode)
}
return nil, env.Error
}
return env.Data, nil
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Map;
public class ImpossibleCreatureGenerator {
static final String TOKEN = "YOUR_TOKEN"; // from /tokens.html
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static final HttpClient HTTP = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(20))
.build();
static class AppException extends RuntimeException {
AppException(String body) { super("app-api: " + body); }
}
/**
* Sends JSON and returns the raw envelope. The JDK has no JSON parser, so hand
* the result to Jackson or Gson and read the `data` member from it.
*/
static String call(String method, String path, String jsonBody, Map<String, String> extra)
throws Exception {
HttpRequest.BodyPublisher pub = jsonBody == null
? HttpRequest.BodyPublishers.noBody()
: HttpRequest.BodyPublishers.ofString(jsonBody);
HttpRequest.Builder b = HttpRequest.newBuilder(URI.create(BASE + path))
.timeout(Duration.ofMinutes(5))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.method(method, pub);
if (extra != null) extra.forEach(b::header);
HttpResponse<String> res = HTTP.send(b.build(), HttpResponse.BodyHandlers.ofString());
String body = res.body();
if (res.statusCode() >= 400 || body.contains("\"ok\":false") || body.contains("\"ok\": false")) {
throw new AppException(body);
}
return body;
}
/** Minimal JSON string escaper, used to build request bodies by hand. */
static String jsonString(String s) {
StringBuilder out = new StringBuilder("\"");
for (char c : s.toCharArray()) {
switch (c) {
case '"' -> out.append("\\\"");
case '\\' -> out.append("\\\\");
case '\n' -> out.append("\\n");
default -> out.append(c);
}
}
return out.append('"').toString();
}
}
require 'base64'
require 'json'
require 'net/http'
require 'uri'
TOKEN = 'YOUR_TOKEN' # from https://impossible-creature-generator.skillsafe.ai/tokens.html
BASE = 'https://api.skillsafe.ai/v1/app-api'
class AppError < StandardError
attr_reader :code, :details
def initialize(err)
@code = err['code'] || 'unknown'
@details = err['details'] || {}
super("#{@code}: #{err['message']}")
end
end
# Sends JSON and returns the unwrapped `data` object.
def call(method, path, body = nil, extra = {})
uri = URI(BASE + path)
req = Net::HTTP.const_get(method.capitalize).new(uri)
req['Authorization'] = "Bearer #{TOKEN}"
req['Content-Type'] = 'application/json'
extra.each { |k, v| req[k] = v }
req.body = JSON.dump(body) unless body.nil?
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, read_timeout: 300) do |http|
http.request(req)
end
env = begin
JSON.parse(res.body)
rescue JSON::ParserError
raise AppError, { 'code' => 'transport', 'message' => "HTTP #{res.code}: #{res.body[0, 200]}" }
end
raise AppError, (env['error'] || { 'message' => res.body[0, 200] }) unless env['ok']
env['data']
end
<?php
$TOKEN = 'YOUR_TOKEN'; // from https://impossible-creature-generator.skillsafe.ai/tokens.html
$BASE = 'https://api.skillsafe.ai/v1/app-api';
class AppError extends RuntimeException {
public string $code;
public array $details;
public function __construct(array $err) {
$this->code = $err['code'] ?? 'unknown';
$this->details = $err['details'] ?? [];
parent::__construct($this->code . ': ' . ($err['message'] ?? ''));
}
}
/** Sends JSON and returns the unwrapped `data` array. */
function sf_call(string $method, string $path, ?array $body = null, array $extra = []): array {
global $TOKEN, $BASE;
$headers = array_merge([
'Authorization: Bearer ' . $TOKEN,
'Content-Type: application/json',
], $extra);
$ch = curl_init($BASE . $path);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 300,
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$env = json_decode((string) $raw, true);
if (!is_array($env)) {
throw new AppError(['code' => 'transport', 'message' => "HTTP $status"]);
}
if (empty($env['ok'])) {
throw new AppError($env['error'] ?? ['message' => substr((string) $raw, 0, 200)]);
}
return $env['data'];
}
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
const string Token = "YOUR_TOKEN"; // from https://impossible-creature-generator.skillsafe.ai/tokens.html
const string BaseUrl = "https://api.skillsafe.ai/v1/app-api";
var http = new HttpClient { Timeout = TimeSpan.FromMinutes(5) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token);
// Sends JSON and returns the unwrapped `data` element.
async Task<JsonElement> Call(HttpMethod method, string path, object body = null,
IEnumerable<KeyValuePair<string, string>> extra = null)
{
using var req = new HttpRequestMessage(method, BaseUrl + path);
if (body != null)
{
req.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
}
if (extra != null)
{
foreach (var h in extra) req.Headers.Add(h.Key, h.Value);
}
using var res = await http.SendAsync(req);
var text = await res.Content.ReadAsStringAsync();
JsonDocument doc;
try { doc = JsonDocument.Parse(text); }
catch (JsonException) { throw new Exception("transport: HTTP " + (int) res.StatusCode); }
using (doc)
{
var env = doc.RootElement;
if (!env.GetProperty("ok").GetBoolean())
{
var err = env.GetProperty("error");
throw new Exception(err.GetProperty("code").GetString() + ": "
+ err.GetProperty("message").GetString());
}
return env.GetProperty("data").Clone();
}
}
3. Check the session and the balance
GET /me is free, instant, and the right first call in any script. It answers
two questions: is this token attached to a real subject, and can it afford what you are about
to do.
/me returns exactly three fields. That is worth stating
flatly, because assuming otherwise is the most-copied mistake against this API:
| Field | Type | Meaning |
|---|---|---|
subject_type | string | "user" or "guest". |
subject_id | string | An opaque identifier. Stable, but not something you can look anything else up with. |
credits | number | The spendable balance right now. |
There is no email, no name, no display name and no id. Code that reaches
for me.email or me.id reads undefined and then fails somewhere far
away from the cause. Signed in means subject_type === "user"
— that comparison is the entire test. A guest subject has a balance too,
but it is the publisher's sponsored allowance rather than an account, and it can run out
halfway through a batch.
SF_TOKEN="YOUR_TOKEN"
curl -sS "https://api.skillsafe.ai/v1/app-api/me" \
-H "Authorization: Bearer $SF_TOKEN"
# {"ok":true,"data":{"subject_type":"user","subject_id":"sub_...","credits":41902}}
# Three fields. That is the whole response.
# Signed in is subject_type == "user". Nothing else in there says so.
curl -sS "https://api.skillsafe.ai/v1/app-api/me" \
-H "Authorization: Bearer $SF_TOKEN" \
| jq -e '.data.subject_type == "user"' >/dev/null \
&& echo "signed in" || echo "guest token"
me = call("GET", "/me")
# -> {"subject_type": "user", "subject_id": "sub_...", "credits": 41902}
# Those three keys are all of it. No email, no name, no id.
signed_in = me["subject_type"] == "user"
if not signed_in:
raise SystemExit("guest token - sign in at https://impossible-creature-generator.skillsafe.ai/ for a real balance")
print("credits:", me["credits"])
const me = await call("GET", "/me");
// -> { subject_type: "user", subject_id: "sub_...", credits: 41902 }
// Those three keys are all of it. No email, no name, no id.
const signedIn = me.subject_type === "user";
if (!signedIn) {
throw new Error("guest token - sign in at https://impossible-creature-generator.skillsafe.ai/ for a real balance");
}
console.log("credits:", me.credits);
type Me struct {
SubjectType string `json:"subject_type"`
SubjectID string `json:"subject_id"`
Credits float64 `json:"credits"`
}
// Three fields, and that is the whole struct. No email, no name, no id.
raw, err := call("GET", "/me", nil, nil)
if err != nil {
log.Fatal(err)
}
var me Me
if err := json.Unmarshal(raw, &me); err != nil {
log.Fatal(err)
}
if me.SubjectType != "user" { // signed in is exactly this comparison
log.Fatal("guest token - sign in for a real balance")
}
fmt.Println("credits:", me.Credits)
// GET /me returns exactly subject_type, subject_id and credits.
// No email, no name, no id - do not model fields that are not there.
String envelope = ImpossibleCreatureGenerator.call("GET", "/me", null, null);
System.out.println(envelope);
// With Jackson:
// JsonNode data = new ObjectMapper().readTree(envelope).get("data");
// boolean signedIn = "user".equals(data.get("subject_type").asText());
// if (!signedIn) throw new IllegalStateException("guest token - sign in for a real balance");
// System.out.println("credits: " + data.get("credits").asLong());
me = call('GET', '/me')
# => {"subject_type"=>"user", "subject_id"=>"sub_...", "credits"=>41902}
# Those three keys are all of it. No email, no name, no id.
signed_in = me['subject_type'] == 'user'
abort 'guest token - sign in for a real balance' unless signed_in
puts "credits: #{me['credits']}"
<?php
$me = sf_call('GET', '/me');
// => ['subject_type' => 'user', 'subject_id' => 'sub_...', 'credits' => 41902]
// Those three keys are all of it. No email, no name, no id.
$signedIn = $me['subject_type'] === 'user';
if (!$signedIn) {
throw new RuntimeException('guest token - sign in for a real balance');
}
echo 'credits: ' . $me['credits'] . PHP_EOL;
var me = await Call(HttpMethod.Get, "/me");
// -> { "subject_type": "user", "subject_id": "sub_...", "credits": 41902 }
// Those three properties are all of it. No email, no name, no id.
var signedIn = me.GetProperty("subject_type").GetString() == "user";
if (!signedIn)
{
throw new InvalidOperationException("guest token - sign in for a real balance");
}
Console.WriteLine("credits: " + me.GetProperty("credits").GetInt64());
4. Estimate before you run
POST /estimate takes the same two-key body you would send to /run
and tells you what that run would reserve. It is free, it creates no job, and it charges
nothing.
| Field | Type | What it tells you |
|---|---|---|
model | string | The concrete model the run would reach. |
model_alias | string | The alias you asked for, echoed back — gpt-image here. Assert on it: if it comes back as something else, your $model key did not survive serialisation and you are about to buy paragraphs of prose instead of a painting. |
markup_bps | number | The publisher's markup in basis points, already folded into the figures below. |
hold_credits | number | What /run would reserve against your balance. |
min_credits | number | The balance floor. Below this the run is rejected with payment_required before anything starts. |
For an image model the hold is per picture and does not vary with prompt
length. A four-word instruction and the full compiled brief from step 6 reserve
exactly the same amount, because the renderer prices pictures, not tokens. Two consequences,
both useful. First, one estimate covers every brief you will ever send: call
it once at start-up and cache the answer for the life of the process. Second, you can estimate
with a placeholder string, before you have written a brief at all. A batch of N
pictures is N separate runs reserving N times hold_credits;
there is no volume discount and no batching endpoint.
hold_credits is a reservation, not a price. It is deliberately
pessimistic — a ceiling on what the run could conceivably cost — and it is taken
out of your available balance for the duration of the job, then released when the job settles.
What you actually pay comes back afterwards as charged_credits on the terminal
job, and it varies substantially from picture to picture: the same brief run twice will not
necessarily settle at the same figure, and the gap between the hold and the settlement is
routinely large. So budget your concurrency against hold_credits, since
that is what governs how many runs you can have in flight at once, and report your
spend from charged_credits, since that is the money.
Do not hard-code any figure from this page into a billing assumption. Read
hold_credits from a live estimate at start-up, and read
charged_credits off each finished job.
SF_TOKEN="YOUR_TOKEN"
# The instruction can be a placeholder: an image hold does not depend on it.
curl -sS -X POST "https://api.skillsafe.ai/v1/app-api/estimate" \
-H "Authorization: Bearer $SF_TOKEN" \
-H "Content-Type: application/json" \
-d '{"instruction": "estimate probe", "$model": "gpt-image"}' | jq '.data'
# {
# "model": "gpt-image-...",
# "model_alias": "gpt-image",
# "markup_bps": 2000,
# "hold_credits": 2652,
# "min_credits": 2652
# }
# Four pictures reserve four times hold_credits, one run at a time.
# What you pay is charged_credits on each finished job, not this number.
# The instruction can be a placeholder: an image hold does not depend on it.
est = call("POST", "/estimate", {"instruction": "estimate probe", "$model": "gpt-image"})
assert est["model_alias"] == "gpt-image", "the $model key did not reach the API"
hold = est["hold_credits"]
print("model {0} reserves {1} credits per picture".format(est["model"], hold))
print("floor {0}, markup {1} bps".format(est["min_credits"], est["markup_bps"]))
# Budget the batch against the hold. What you pay is charged_credits, later, per job.
batch = 4
me = call("GET", "/me")
if me["credits"] < hold * batch:
raise SystemExit("need {0} credits to hold {1} pictures, have {2}".format(
hold * batch, batch, me["credits"]))
// The instruction can be a placeholder: an image hold does not depend on it.
const est = await call("POST", "/estimate", {
instruction: "estimate probe",
$model: "gpt-image",
});
if (est.model_alias !== "gpt-image") throw new Error("the $model key did not reach the API");
const hold = est.hold_credits;
console.log(`model ${est.model} reserves ${hold} credits per picture`);
console.log(`floor ${est.min_credits}, markup ${est.markup_bps} bps`);
// Budget the batch against the hold. What you pay is charged_credits, later, per job.
const batch = 4;
const me = await call("GET", "/me");
if (me.credits < hold * batch) {
throw new Error(`need ${hold * batch} credits to hold ${batch} pictures, have ${me.credits}`);
}
type Estimate struct {
Model string `json:"model"`
ModelAlias string `json:"model_alias"`
MarkupBps float64 `json:"markup_bps"`
HoldCredits float64 `json:"hold_credits"`
MinCredits float64 `json:"min_credits"`
}
// The instruction can be a placeholder: an image hold does not depend on it.
raw, err := call("POST", "/estimate", map[string]any{
"instruction": "estimate probe",
"$model": "gpt-image",
}, nil)
if err != nil {
log.Fatal(err)
}
var est Estimate
if err := json.Unmarshal(raw, &est); err != nil {
log.Fatal(err)
}
if est.ModelAlias != "gpt-image" {
log.Fatal("the $model key did not reach the API")
}
// Budget concurrency against the hold; report spend from charged_credits later.
fmt.Printf("%s reserves %.0f per picture, floor %.0f, markup %.0f bps\n",
est.Model, est.HoldCredits, est.MinCredits, est.MarkupBps)
// The instruction can be a placeholder: an image hold does not depend on it.
String body = "{\"instruction\": \"estimate probe\", \"$model\": \"gpt-image\"}";
String envelope = ImpossibleCreatureGenerator.call("POST", "/estimate", body, null);
System.out.println(envelope);
// With Jackson:
// JsonNode d = new ObjectMapper().readTree(envelope).get("data");
// if (!"gpt-image".equals(d.get("model_alias").asText())) {
// throw new IllegalStateException("the $model key did not reach the API");
// }
// long hold = d.get("hold_credits").asLong(); // reserved, per picture
// long floor = d.get("min_credits").asLong(); // balance floor
// long markup = d.get("markup_bps").asLong();
// // Budget concurrency against hold; read charged_credits off the finished job.
# The instruction can be a placeholder: an image hold does not depend on it.
est = call('POST', '/estimate', { 'instruction' => 'estimate probe', '$model' => 'gpt-image' })
raise 'the $model key did not reach the API' unless est['model_alias'] == 'gpt-image'
hold = est['hold_credits']
puts "model #{est['model']} reserves #{hold} credits per picture"
puts "floor #{est['min_credits']}, markup #{est['markup_bps']} bps"
# Budget the batch against the hold. What you pay is charged_credits, later, per job.
batch = 4
me = call('GET', '/me')
abort "need #{hold * batch} credits to hold #{batch} pictures, have #{me['credits']}" if
me['credits'] < hold * batch
<?php
// The instruction can be a placeholder: an image hold does not depend on it.
$est = sf_call('POST', '/estimate', [
'instruction' => 'estimate probe',
'$model' => 'gpt-image',
]);
if ($est['model_alias'] !== 'gpt-image') {
throw new RuntimeException('the $model key did not reach the API');
}
$hold = $est['hold_credits'];
printf("model %s reserves %s credits per picture\n", $est['model'], $hold);
printf("floor %s, markup %s bps\n", $est['min_credits'], $est['markup_bps']);
// Budget the batch against the hold. What you pay is charged_credits, later, per job.
$batch = 4;
$me = sf_call('GET', '/me');
if ($me['credits'] < $hold * $batch) {
throw new RuntimeException("need " . ($hold * $batch) . " credits to hold $batch pictures");
}
// The instruction can be a placeholder: an image hold does not depend on it.
var est = await Call(HttpMethod.Post, "/estimate", new Dictionary<string, object> {
["instruction"] = "estimate probe",
["$model"] = "gpt-image",
});
if (est.GetProperty("model_alias").GetString() != "gpt-image")
{
throw new InvalidOperationException("the $model key did not reach the API");
}
var hold = est.GetProperty("hold_credits").GetInt64();
Console.WriteLine($"{est.GetProperty("model").GetString()} reserves {hold} credits per picture");
Console.WriteLine($"floor {est.GetProperty("min_credits").GetInt64()}, "
+ $"markup {est.GetProperty("markup_bps").GetInt64()} bps");
// Budget the batch against the hold. What you pay is charged_credits, later, per job.
var batch = 4;
var me = await Call(HttpMethod.Get, "/me");
if (me.GetProperty("credits").GetInt64() < hold * batch)
{
throw new InvalidOperationException($"need {hold * batch} credits to hold {batch} pictures");
}
5. Paint a picture: /run, then poll
POST /run does not wait. It reserves the hold, queues the job, and returns
{"job_id": "..."} straight away. You then poll GET /jobs/{job_id}
until status is succeeded or failed.
| Field on the job | When it appears | Meaning |
|---|---|---|
job_id | Always | The handle you poll with. |
status | Always | queued, running, succeeded, failed. The first two are non-terminal; keep polling. |
output | On succeeded | Holds images and output. |
charged_credits | On any terminal status | What you actually paid once the hold was released. This, not hold_credits, is your spend. |
error | On failed | The same code and message shape as the envelope error. |
Poll every 1.5 seconds and give up after 240. A painting normally lands in
20 to 40 seconds. Polling tighter buys you nothing and will earn you a
rate_limited; a shorter ceiling abandons jobs that were about to succeed and that
you have already paid for.
The picture is base64 in the job output. Read
output.images[0].b64 for the bytes and
output.images[0].content_type for what to name the file. Decode the base64 and
write it as binary; do not write it as text and do not run it through a string-encoding step
on the way to disk.
output.output is the empty string on an image run. That field
is where the platform's text lane puts its answer, and every habit carried over from a text app
reaches for it first. Here it is present, it is a string, and it is "" — not
null, not missing — so a truthiness check on it will never tell you the run failed. It
will just quietly hand you nothing. Look in images, always.
Idempotency
Send an Idempotency-Key header on POST /run and a retry after a
dropped connection attaches to the job you already started instead of paying for a second one.
But replaying a key returns the original job even when that job failed. A key
derived only from your inputs therefore turns a transient renderer failure into a permanent
one: every retry re-serves the same dead job, forever.
Derive the key from a hash of the brief plus an attempt counter. The hash makes
accidental duplicates of the same request collapse into one charge; the counter makes a
deliberate retry a genuinely new run. Something of the shape
sha256(brief)[:16] + ":attempt-" + n is enough.
SF_TOKEN="YOUR_TOKEN"
BASE="https://api.skillsafe.ai/v1/app-api"
# Exactly two keys. The brief goes in a file so the shell never has to quote it.
cat > body.json <<'JSON'
{"instruction": "A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist. The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand. The animal stands on two hind limbs with a long stiffened tail as a counterweight. It weighs about 45 kg. It lives on grassland that spends part of every year underwater. It works through fallen and decaying material. It moves in bounds on enlarged hind limbs. It has bare glandular skin with no covering at all. It keeps its core warm and lets its extremities run near freezing. Those facts settle the rest of it: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped. There is no lettering anywhere in the picture: no species name, no caption, no annotation, no numbered key, no scale bar, no signature and no border, in any alphabet or language.", "$model": "gpt-image"}
JSON
# Hash of the brief, plus an attempt counter. The counter is what lets a
# deliberate retry be a new run instead of a replay of a failed one.
ATTEMPT=1
DIGEST=$(jq -r '.instruction' body.json | shasum -a 256 | cut -c1-16)
IDEM="$DIGEST:attempt-$ATTEMPT"
JOB=$(curl -sS -X POST "$BASE/run" \
-H "Authorization: Bearer $SF_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEM" \
--data-binary @body.json | jq -r '.data.job_id')
echo "job $JOB"
DEADLINE=$(( $(date +%s) + 240 ))
while :; do
RES=$(curl -sS "$BASE/jobs/$JOB" -H "Authorization: Bearer $SF_TOKEN")
STATUS=$(printf '%s' "$RES" | jq -r '.data.status')
[ "$STATUS" = "succeeded" ] && break
[ "$STATUS" = "failed" ] && { printf '%s' "$RES" | jq '.data.error'; exit 1; }
[ "$(date +%s)" -ge "$DEADLINE" ] && { echo "timed out after 240s" >&2; exit 1; }
sleep 1.5
done
printf '%s' "$RES" | jq -r '.data.output.images[0].b64' | base64 --decode > chair.png
printf '%s' "$RES" | jq -r '"type \(.data.output.images[0].content_type), charged \(.data.charged_credits)"'
# .data.output.output is "" on an image run - the picture is only in images[0].
import base64
import hashlib
import time
BRIEF = "A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist. The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand. The animal stands on two hind limbs with a long stiffened tail as a counterweight. It weighs about 45 kg. It lives on grassland that spends part of every year underwater. It works through fallen and decaying material. It moves in bounds on enlarged hind limbs. It has bare glandular skin with no covering at all. It keeps its core warm and lets its extremities run near freezing. Those facts settle the rest of it: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped. There is no lettering anywhere in the picture: no species name, no caption, no annotation, no numbered key, no scale bar, no signature and no border, in any alphabet or language."
def paint(brief, attempt=1, path="chair.png"):
# Hash of the brief plus an attempt counter: duplicates collapse, retries do not.
digest = hashlib.sha256(brief.encode("utf-8")).hexdigest()[:16]
key = "{0}:attempt-{1}".format(digest, attempt)
started = call("POST", "/run",
{"instruction": brief, "$model": "gpt-image"}, # exactly two keys
headers={"Idempotency-Key": key})
job_id = started["job_id"]
deadline = time.time() + 240 # a painting normally lands in 20-40s
while True:
job = call("GET", "/jobs/" + job_id)
if job["status"] == "succeeded":
break
if job["status"] == "failed":
raise AppError(job.get("error") or {"code": "failed", "message": job_id})
if time.time() > deadline:
raise TimeoutError("job {0} still {1} after 240s".format(job_id, job["status"]))
time.sleep(1.5)
image = job["output"]["images"][0]
with open(path, "wb") as fh: # binary, always
fh.write(base64.b64decode(image["b64"]))
# job["output"]["output"] is "" here - do not look for the picture in it.
print(path, image["content_type"], "charged", job["charged_credits"])
return path
paint(BRIEF)
import { writeFile } from "node:fs/promises";
import { createHash } from "node:crypto";
const BRIEF = "A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist. The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand. The animal stands on two hind limbs with a long stiffened tail as a counterweight. It weighs about 45 kg. It lives on grassland that spends part of every year underwater. It works through fallen and decaying material. It moves in bounds on enlarged hind limbs. It has bare glandular skin with no covering at all. It keeps its core warm and lets its extremities run near freezing. Those facts settle the rest of it: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped. There is no lettering anywhere in the picture: no species name, no caption, no annotation, no numbered key, no scale bar, no signature and no border, in any alphabet or language.";
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function paint(brief, attempt = 1, path = "chair.png") {
// Hash of the brief plus an attempt counter: duplicates collapse, retries do not.
const digest = createHash("sha256").update(brief).digest("hex").slice(0, 16);
const key = `${digest}:attempt-${attempt}`;
const started = await call(
"POST",
"/run",
{ instruction: brief, $model: "gpt-image" }, // exactly two keys
{ "Idempotency-Key": key },
);
const deadline = Date.now() + 240_000; // a painting normally lands in 20-40s
let job;
for (;;) {
job = await call("GET", `/jobs/${started.job_id}`);
if (job.status === "succeeded") break;
if (job.status === "failed") {
throw new AppError(job.error || { code: "failed", message: started.job_id });
}
if (Date.now() > deadline) {
throw new Error(`job ${started.job_id} still ${job.status} after 240s`);
}
await sleep(1500);
}
const image = job.output.images[0];
await writeFile(path, Buffer.from(image.b64, "base64")); // binary, always
// job.output.output is "" here - the picture is only in images[0].
console.log(path, image.content_type, "charged", job.charged_credits);
return path;
}
await paint(BRIEF);
const Brief = "A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist. The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand. The animal stands on two hind limbs with a long stiffened tail as a counterweight. It weighs about 45 kg. It lives on grassland that spends part of every year underwater. It works through fallen and decaying material. It moves in bounds on enlarged hind limbs. It has bare glandular skin with no covering at all. It keeps its core warm and lets its extremities run near freezing. Those facts settle the rest of it: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped. There is no lettering anywhere in the picture: no species name, no caption, no annotation, no numbered key, no scale bar, no signature and no border, in any alphabet or language."
type Image struct {
B64 string `json:"b64"`
ContentType string `json:"content_type"`
}
type Job struct {
JobID string `json:"job_id"`
Status string `json:"status"`
Output struct {
Images []Image `json:"images"`
Output string `json:"output"` // "" on an image run
} `json:"output"`
ChargedCredits float64 `json:"charged_credits"`
Error *apiError `json:"error"`
}
func paint(brief, path string, attempt int) error {
// Hash of the brief plus an attempt counter: duplicates collapse, retries do not.
sum := sha256.Sum256([]byte(brief))
key := fmt.Sprintf("%x:attempt-%d", sum[:8], attempt)
body := map[string]any{"instruction": brief, "$model": "gpt-image"} // exactly two keys
raw, err := call("POST", "/run", body, map[string]string{"Idempotency-Key": key})
if err != nil {
return err
}
var started struct {
JobID string `json:"job_id"`
}
if err := json.Unmarshal(raw, &started); err != nil {
return err
}
deadline := time.Now().Add(240 * time.Second) // a painting lands in 20-40s
var job Job
for {
raw, err = call("GET", "/jobs/"+started.JobID, nil, nil)
if err != nil {
return err
}
if err := json.Unmarshal(raw, &job); err != nil {
return err
}
if job.Status == "succeeded" {
break
}
if job.Status == "failed" {
return job.Error
}
if time.Now().After(deadline) {
return fmt.Errorf("job %s still %s after 240s", started.JobID, job.Status)
}
time.Sleep(1500 * time.Millisecond)
}
pixels, err := base64.StdEncoding.DecodeString(job.Output.Images[0].B64)
if err != nil {
return err
}
if err := os.WriteFile(path, pixels, 0o644); err != nil {
return err
}
// job.Output.Output is "" here - the picture is only in Images[0].
fmt.Println(path, job.Output.Images[0].ContentType, "charged", job.ChargedCredits)
return nil
}
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.HexFormat;
import java.util.Map;
// The compiled plate brief from step 6.
static final String BRIEF = "A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist. The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand. The animal stands on two hind limbs with a long stiffened tail as a counterweight. It weighs about 45 kg. It lives on grassland that spends part of every year underwater. It works through fallen and decaying material. It moves in bounds on enlarged hind limbs. It has bare glandular skin with no covering at all. It keeps its core warm and lets its extremities run near freezing. Those facts settle the rest of it: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped. There is no lettering anywhere in the picture: no species name, no caption, no annotation, no numbered key, no scale bar, no signature and no border, in any alphabet or language.";
static Path paint(String brief, int attempt, Path out) throws Exception {
// Hash of the brief plus an attempt counter: duplicates collapse, retries do not.
byte[] sum = MessageDigest.getInstance("SHA-256").digest(brief.getBytes("UTF-8"));
String key = HexFormat.of().formatHex(sum).substring(0, 16) + ":attempt-" + attempt;
Map<String, String> idem = Map.of("Idempotency-Key", key);
// Exactly two keys.
String body = "{\"instruction\": " + ImpossibleCreatureGenerator.jsonString(brief) + ", \"$model\": \"gpt-image\"}";
String startedEnv = ImpossibleCreatureGenerator.call("POST", "/run", body, idem);
// Parse with Jackson or Gson; shown here as the fields you need.
String jobId = readString(startedEnv, "data", "job_id");
long deadline = System.currentTimeMillis() + 240_000L; // 20-40s is typical
String jobEnv;
String status;
while (true) {
jobEnv = ImpossibleCreatureGenerator.call("GET", "/jobs/" + jobId, null, null);
status = readString(jobEnv, "data", "status");
if ("succeeded".equals(status)) break;
if ("failed".equals(status)) throw new IllegalStateException("job failed: " + jobEnv);
if (System.currentTimeMillis() > deadline) {
throw new IllegalStateException("job " + jobId + " still " + status + " after 240s");
}
Thread.sleep(1500L);
}
// data.output.images[0].b64, and data.output.output is "" on an image run.
String b64 = readString(jobEnv, "data", "output", "images", "0", "b64");
Files.write(out, Base64.getDecoder().decode(b64));
System.out.println(out + " charged " + readString(jobEnv, "data", "charged_credits"));
return out;
}
require 'digest'
BRIEF = 'A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist. The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand. The animal stands on two hind limbs with a long stiffened tail as a counterweight. It weighs about 45 kg. It lives on grassland that spends part of every year underwater. It works through fallen and decaying material. It moves in bounds on enlarged hind limbs. It has bare glandular skin with no covering at all. It keeps its core warm and lets its extremities run near freezing. Those facts settle the rest of it: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped. There is no lettering anywhere in the picture: no species name, no caption, no annotation, no numbered key, no scale bar, no signature and no border, in any alphabet or language.'
def paint(brief, attempt = 1, path = 'chair.png')
# Hash of the brief plus an attempt counter: duplicates collapse, retries do not.
key = "#{Digest::SHA256.hexdigest(brief)[0, 16]}:attempt-#{attempt}"
# Exactly two keys.
started = call('POST', '/run',
{ 'instruction' => brief, '$model' => 'gpt-image' },
{ 'Idempotency-Key' => key })
job_id = started['job_id']
deadline = Time.now + 240 # a painting normally lands in 20-40s
job = nil
loop do
job = call('GET', "/jobs/#{job_id}")
break if job['status'] == 'succeeded'
raise AppError, (job['error'] || { 'code' => 'failed', 'message' => job_id }) if job['status'] == 'failed'
raise "job #{job_id} still #{job['status']} after 240s" if Time.now > deadline
sleep 1.5
end
image = job['output']['images'][0]
File.binwrite(path, Base64.decode64(image['b64'])) # binary, always
# job['output']['output'] is '' here - the picture is only in images[0].
puts "#{path} #{image['content_type']} charged #{job['charged_credits']}"
path
end
paint(BRIEF)
<?php
$BRIEF = 'A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist. The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand. The animal stands on two hind limbs with a long stiffened tail as a counterweight. It weighs about 45 kg. It lives on grassland that spends part of every year underwater. It works through fallen and decaying material. It moves in bounds on enlarged hind limbs. It has bare glandular skin with no covering at all. It keeps its core warm and lets its extremities run near freezing. Those facts settle the rest of it: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped. There is no lettering anywhere in the picture: no species name, no caption, no annotation, no numbered key, no scale bar, no signature and no border, in any alphabet or language.';
function paint(string $brief, int $attempt = 1, string $path = 'chair.png'): string {
// Hash of the brief plus an attempt counter: duplicates collapse, retries do not.
$key = substr(hash('sha256', $brief), 0, 16) . ':attempt-' . $attempt;
// Exactly two keys. Single-quote '$model' so PHP does not interpolate it.
$started = sf_call('POST', '/run', [
'instruction' => $brief,
'$model' => 'gpt-image',
], ['Idempotency-Key: ' . $key]);
$jobId = $started['job_id'];
$deadline = time() + 240; // a painting normally lands in 20-40s
while (true) {
$job = sf_call('GET', '/jobs/' . $jobId);
if ($job['status'] === 'succeeded') break;
if ($job['status'] === 'failed') {
throw new AppError($job['error'] ?? ['code' => 'failed', 'message' => $jobId]);
}
if (time() > $deadline) {
throw new RuntimeException("job $jobId still {$job['status']} after 240s");
}
usleep(1_500_000);
}
$image = $job['output']['images'][0];
file_put_contents($path, base64_decode($image['b64']));
// $job['output']['output'] is '' here - look in images[0] only.
printf("%s %s charged %s\n", $path, $image['content_type'], $job['charged_credits']);
return $path;
}
paint($BRIEF);
using System.IO;
using System.Security.Cryptography;
const string Brief = "A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist. The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand. The animal stands on two hind limbs with a long stiffened tail as a counterweight. It weighs about 45 kg. It lives on grassland that spends part of every year underwater. It works through fallen and decaying material. It moves in bounds on enlarged hind limbs. It has bare glandular skin with no covering at all. It keeps its core warm and lets its extremities run near freezing. Those facts settle the rest of it: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped. There is no lettering anywhere in the picture: no species name, no caption, no annotation, no numbered key, no scale bar, no signature and no border, in any alphabet or language.";
async Task<string> Paint(string brief, int attempt = 1, string path = "chair.png")
{
// Hash of the brief plus an attempt counter: duplicates collapse, retries do not.
var sum = SHA256.HashData(Encoding.UTF8.GetBytes(brief));
var key = Convert.ToHexString(sum)[..16].ToLowerInvariant() + ":attempt-" + attempt;
// Exactly two keys.
var body = new Dictionary<string, object> {
["instruction"] = brief,
["$model"] = "gpt-image",
};
var started = await Call(HttpMethod.Post, "/run", body,
new[] { new KeyValuePair<string, string>("Idempotency-Key", key) });
var jobId = started.GetProperty("job_id").GetString();
var deadline = DateTime.UtcNow.AddSeconds(240); // a painting lands in 20-40s
JsonElement job;
while (true)
{
job = await Call(HttpMethod.Get, "/jobs/" + jobId);
var status = job.GetProperty("status").GetString();
if (status == "succeeded") break;
if (status == "failed") throw new Exception("job failed: " + job.GetProperty("error"));
if (DateTime.UtcNow > deadline) throw new TimeoutException("job " + jobId + " still " + status);
await Task.Delay(1500);
}
var image = job.GetProperty("output").GetProperty("images")[0];
await File.WriteAllBytesAsync(path, Convert.FromBase64String(image.GetProperty("b64").GetString()));
// output.output is "" here - the picture is only in images[0].
Console.WriteLine($"{path} {image.GetProperty("content_type").GetString()} "
+ $"charged {job.GetProperty("charged_credits").GetInt64()}");
return path;
}
await Paint(Brief);
6. Composing a brief that reads as a field-guide plate
The API contract is four paragraphs long. The interesting problem is the other side of it:
what to put in instruction. Impossible Creature Generator's whole design assumption is that a
creature is convincing in proportion to how much of it agrees with the rest of it.
A bag of borrowed parts reads as collage no matter how well it is painted. An animal whose
teeth, gut, limbs and covering are all answering the same question reads as zoology.
So do not ask for "a mythical beast" or "an impossible creature". Those are labels for the effect, and a model given a label paints the label — a griffin, a chimera, a winged lion, the whole exhausted bestiary. State instead the eight decisions that actually determine an animal's shape, one short sentence each, in ordinary words.
Body plan — symmetry, skeleton type, how many limbs and what they are for. This constrains everything downstream: a radial animal has no leading end, and a body held in shape by fluid pressure cannot stand up out of water. Habitat — not a mood, a list of problems: a medium, a temperature, a substrate, a light budget. Adult mass — the axis that does the most work and gets the least attention; give a number. Feeding guild — what it eats, which is a hardware requirement rather than a preference. Locomotion, body covering and thermal strategy — one sentence each. Then plate style: the drawing convention is part of the evidence, and a hand-coloured lithograph reads as an expedition record in a way that "fantasy art" never will.
Then derive the consequences and state them as requirements. This is the step almost everyone skips, and skipping it is why most generated creatures look like costumes. A bulk grazer needs molars worn to flat plates and growing all its life, and a fermentation chamber occupying most of the trunk — so it is barrel-shaped, and that is not a stylistic choice. An ambush predator needs recurved holding teeth, a short simple gut and therefore a narrow waist. Write those out. A model given the cause and the effect draws both; a model given only the cause draws neither.
Check the combination before you spend anything. Most striking creature ideas are impossible for a reason that has a number attached, and the number is usually mass. The heaviest animal that has ever flown weighed about 16 kg, so a flying creature of 300 kg is not bold, it is arithmetic that does not close. Cuticle mass rises faster than the volume it encloses, which caps land arthropods near 4 kg. Bulk fermentation needs a chamber, which puts a floor near a kilogramme under any grazer. The browser app runs twenty-two of these rules before it will render; from a script you should at minimum check mass against your locomotor mode.
Close with a negative clause about lettering, and make it long. "A plate from a field guide" is about the strongest cue there is for painting handwritten species names, a scale bar and a numbered key, because that is exactly what the reference material looks like. Left unsaid, the picture comes back captioned in invented Latin and the run is wasted.
A worked example, about a hundred and forty words, which is roughly the right length — long enough to fix all eight axes and their consequences, short enough that no single sentence gets outvoted:
A single plate from a naturalist's field guide to a world that does
not exist. One invented animal, drawn as though from a specimen in
front of the artist.
The plate is a hand-coloured lithograph: fine stone-drawn outline
with transparent watercolour laid over it by hand.
The animal stands on two hind limbs with a long stiffened tail as a
counterweight. It weighs about 45 kg. It lives on grassland that
spends part of every year underwater. It works through fallen and
decaying material. It moves in bounds on enlarged hind limbs. It has
bare glandular skin with no covering at all. It keeps its core warm
and lets its extremities run near freezing.
Those facts settle the rest of it, and the following are to be drawn
exactly as given: small blunt uniform teeth, with the real processing
happening in the gut; a long capacious gut with a heavy caecum,
giving a soft rounded underline; conspicuous thermal windows where
heat is dumped, and a visible thickening at the base of each limb.
There is no lettering anywhere in the picture: no species name, no
caption, no annotation, no numbered key, no scale bar, no signature
and no border, in any alphabet or language.
Read it back and notice what it does not contain: no word for the overall effect, no "mythical", no "fantastical", no artist's name and no parameters. It is one block of plain prose, which is the only thing the API takes. The code below assembles it from the eight axes so you can vary one at a time — hold seven fixed, sweep the eighth, and you have a controlled experiment instead of a slot machine.
# Eight axes, one sentence each, in this order. jq joins them into the brief
# and writes the two-key body in the same pass.
cat > axes.json <<'JSON'
{
"frame": "A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist.",
"plate": "The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand.",
"plan": "The animal stands on two hind limbs with a long stiffened tail as a counterweight.",
"mass": "It weighs about 45 kg.",
"habitat": "It lives on grassland that spends part of every year underwater.",
"feeding": "It works through fallen and decaying material.",
"locomotion": "It moves in bounds on enlarged hind limbs.",
"integument": "It has bare glandular skin with no covering at all.",
"thermo": "It keeps its core warm and lets its extremities run near freezing.",
"derived": "Those facts settle the rest of it, and the following are to be drawn exactly as given: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped."
}
JSON
NEG="There is no lettering anywhere in the picture: no species name, no caption, no annotation, no numbered key, no scale bar, no signature and no border, in any alphabet or language."
jq --arg neg "$NEG" '
[.frame, .plate, .plan, .mass, .habitat, .feeding, .locomotion,
.integument, .thermo, .derived, $neg]
| join(" ")
| {instruction: ., "$model": "gpt-image"}
' axes.json > body.json
jq -r '.instruction' body.json | wc -w # aim for roughly 140
# Eight axes, one sentence each. Sweep one, hold the others, and you have a
# controlled experiment rather than a slot machine.
AXES = {
"frame": "A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist.",
"plate": "The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand.",
"plan": "The animal stands on two hind limbs with a long stiffened tail as a counterweight.",
"mass": "It weighs about 45 kg.",
"habitat": "It lives on grassland that spends part of every year underwater.",
"feeding": "It works through fallen and decaying material.",
"locomotion": "It moves in bounds on enlarged hind limbs.",
"integument": "It has bare glandular skin with no covering at all.",
"thermo": "It keeps its core warm and lets its extremities run near freezing.",
"derived": "Those facts settle the rest of it, and the following are to be drawn exactly as given: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped.",
}
ORDER = ["frame", "plate", "plan", "mass", "habitat", "feeding",
"locomotion", "integument", "thermo", "derived"]
NEGATIVES = ("There is no lettering anywhere in the picture: no species name, no caption, "
"no annotation, no numbered key, no scale bar, no signature and no border, "
"in any alphabet or language.")
# The cheapest coherence check there is, and the one that catches most bad ideas.
FLIGHT_CEILING_KG = 16.0
def check(mass_kg, flies):
"""Refuse the arithmetic that does not close before spending a credit."""
if flies and mass_kg > FLIGHT_CEILING_KG:
raise ValueError(
"%.0f kg is past the %.0f kg ceiling for powered flight — "
"make it lighter, make it a glider, or make the wings a display structure"
% (mass_kg, FLIGHT_CEILING_KG))
def compile_brief(axes):
"""Join the axes into the one block of prose the API takes."""
return " ".join([axes[k] for k in ORDER] + [NEGATIVES])
check(45.0, flies=False)
BRIEF = compile_brief(AXES)
print(len(BRIEF.split()), "words") # aim for roughly 140
# Sweep the plate style, hold the animal fixed.
for plate in ["The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand.",
"The plate is an engraved figure in pure line, every value built from hatching cut with a burin.",
"The plate is drawn entirely in ink stipple, the whole of the modelling made of dots."]:
variant = dict(AXES, plate=plate)
draw(compile_brief(variant))
// Eight axes, one sentence each. Sweep one, hold the others.
const AXES = {
frame: "A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist.",
plate: "The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand.",
plan: "The animal stands on two hind limbs with a long stiffened tail as a counterweight.",
mass: "It weighs about 45 kg.",
habitat: "It lives on grassland that spends part of every year underwater.",
feeding: "It works through fallen and decaying material.",
locomotion: "It moves in bounds on enlarged hind limbs.",
integument: "It has bare glandular skin with no covering at all.",
thermo: "It keeps its core warm and lets its extremities run near freezing.",
derived: "Those facts settle the rest of it, and the following are to be drawn exactly as given: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped."
};
const ORDER = ["frame", "plate", "plan", "mass", "habitat", "feeding",
"locomotion", "integument", "thermo", "derived"];
const NEGATIVES = "There is no lettering anywhere in the picture: no species name, no caption, " +
"no annotation, no numbered key, no scale bar, no signature and no border, in any alphabet or language.";
const FLIGHT_CEILING_KG = 16;
function check(massKg, flies) {
if (flies && massKg > FLIGHT_CEILING_KG) {
throw new Error(massKg + " kg is past the " + FLIGHT_CEILING_KG +
" kg ceiling for powered flight — lighter, gliding, or wings for display only");
}
}
function compileBrief(axes) {
return ORDER.map((k) => axes[k]).concat([NEGATIVES]).join(" ");
}
check(45, false);
const brief = compileBrief(AXES);
console.log(brief.split(/\s+/).length, "words"); // aim for roughly 140
// Sweep the plate style, hold the animal fixed.
for (const plate of [
"The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand.",
"The plate is an engraved figure in pure line, every value built from hatching cut with a burin.",
"The plate is drawn entirely in ink stipple, the whole of the modelling made of dots."
]) {
await draw(compileBrief({ ...AXES, plate }));
}
// Eight axes, one sentence each. Sweep one, hold the others.
package main
import (
"fmt"
"strings"
)
var order = []string{"frame", "plate", "plan", "mass", "habitat", "feeding",
"locomotion", "integument", "thermo", "derived"}
var axes = map[string]string{
"frame": "A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist.",
"plate": "The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand.",
"plan": "The animal stands on two hind limbs with a long stiffened tail as a counterweight.",
"mass": "It weighs about 45 kg.",
"habitat": "It lives on grassland that spends part of every year underwater.",
"feeding": "It works through fallen and decaying material.",
"locomotion": "It moves in bounds on enlarged hind limbs.",
"integument": "It has bare glandular skin with no covering at all.",
"thermo": "It keeps its core warm and lets its extremities run near freezing.",
"derived": "Those facts settle the rest of it, and the following are to be drawn exactly as given: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped.",
}
const negatives = "There is no lettering anywhere in the picture: no species name, no caption, " +
"no annotation, no numbered key, no scale bar, no signature and no border, in any alphabet or language."
const flightCeilingKg = 16.0
func check(massKg float64, flies bool) error {
if flies && massKg > flightCeilingKg {
return fmt.Errorf("%.0f kg is past the %.0f kg ceiling for powered flight", massKg, flightCeilingKg)
}
return nil
}
func compileBrief(a map[string]string) string {
parts := make([]string, 0, len(order)+1)
for _, k := range order {
parts = append(parts, a[k])
}
return strings.Join(append(parts, negatives), " ")
}
func main() {
if err := check(45, false); err != nil {
panic(err)
}
brief := compileBrief(axes)
fmt.Println(len(strings.Fields(brief)), "words") // aim for roughly 140
draw(brief)
}
// Eight axes, one sentence each. Sweep one, hold the others.
import java.util.*;
import java.util.stream.Collectors;
public class Brief {
static final List<String> ORDER = List.of("frame", "plate", "plan", "mass", "habitat",
"feeding", "locomotion", "integument", "thermo", "derived");
static final Map<String, String> AXES = Map.ofEntries(
Map.entry("frame", "A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist."),
Map.entry("plate", "The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand."),
Map.entry("plan", "The animal stands on two hind limbs with a long stiffened tail as a counterweight."),
Map.entry("mass", "It weighs about 45 kg."),
Map.entry("habitat", "It lives on grassland that spends part of every year underwater."),
Map.entry("feeding", "It works through fallen and decaying material."),
Map.entry("locomotion", "It moves in bounds on enlarged hind limbs."),
Map.entry("integument", "It has bare glandular skin with no covering at all."),
Map.entry("thermo", "It keeps its core warm and lets its extremities run near freezing."),
Map.entry("derived", "Those facts settle the rest of it, and the following are to be drawn exactly as given: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped.")
);
static final String NEGATIVES = "There is no lettering anywhere in the picture: no species "
+ "name, no caption, no annotation, no numbered key, no scale bar, no signature and no "
+ "border, in any alphabet or language.";
static final double FLIGHT_CEILING_KG = 16.0;
static void check(double massKg, boolean flies) {
if (flies && massKg > FLIGHT_CEILING_KG) {
throw new IllegalArgumentException(massKg + " kg is past the ceiling for powered flight");
}
}
static String compileBrief(Map<String, String> axes) {
String body = ORDER.stream().map(axes::get).collect(Collectors.joining(" "));
return body + " " + NEGATIVES;
}
public static void main(String[] args) throws Exception {
check(45.0, false);
String brief = compileBrief(AXES);
System.out.println(brief.split("\\s+").length + " words"); // aim for roughly 140
draw(brief);
}
}
# Eight axes, one sentence each. Sweep one, hold the others.
AXES = {
frame: "A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist.",
plate: "The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand.",
plan: "The animal stands on two hind limbs with a long stiffened tail as a counterweight.",
mass: "It weighs about 45 kg.",
habitat: "It lives on grassland that spends part of every year underwater.",
feeding: "It works through fallen and decaying material.",
locomotion: "It moves in bounds on enlarged hind limbs.",
integument: "It has bare glandular skin with no covering at all.",
thermo: "It keeps its core warm and lets its extremities run near freezing.",
derived: "Those facts settle the rest of it, and the following are to be drawn exactly as given: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped."
}.freeze
ORDER = %i[frame plate plan mass habitat feeding locomotion integument thermo derived].freeze
NEGATIVES = "There is no lettering anywhere in the picture: no species name, no caption, " \
"no annotation, no numbered key, no scale bar, no signature and no border, " \
"in any alphabet or language."
FLIGHT_CEILING_KG = 16.0
def check(mass_kg, flies:)
return unless flies && mass_kg > FLIGHT_CEILING_KG
raise ArgumentError, "#{mass_kg} kg is past the #{FLIGHT_CEILING_KG} kg ceiling for powered flight"
end
def compile_brief(axes)
(ORDER.map { |k| axes[k] } + [NEGATIVES]).join(" ")
end
check(45.0, flies: false)
brief = compile_brief(AXES)
puts "#{brief.split.size} words" # aim for roughly 140
# Sweep the plate style, hold the animal fixed.
["The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand.",
"The plate is an engraved figure in pure line, every value built from hatching cut with a burin.",
"The plate is drawn entirely in ink stipple, the whole of the modelling made of dots."].each do |plate|
draw(compile_brief(AXES.merge(plate: plate)))
end
<?php
// Eight axes, one sentence each. Sweep one, hold the others.
$AXES = [
'frame' => "A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist.",
'plate' => 'The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand.',
'plan' => 'The animal stands on two hind limbs with a long stiffened tail as a counterweight.',
'mass' => 'It weighs about 45 kg.',
'habitat' => 'It lives on grassland that spends part of every year underwater.',
'feeding' => 'It works through fallen and decaying material.',
'locomotion' => 'It moves in bounds on enlarged hind limbs.',
'integument' => 'It has bare glandular skin with no covering at all.',
'thermo' => 'It keeps its core warm and lets its extremities run near freezing.',
'derived' => 'Those facts settle the rest of it, and the following are to be drawn exactly as given: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped.',
];
$ORDER = ['frame', 'plate', 'plan', 'mass', 'habitat', 'feeding',
'locomotion', 'integument', 'thermo', 'derived'];
$NEGATIVES = 'There is no lettering anywhere in the picture: no species name, no caption, '
. 'no annotation, no numbered key, no scale bar, no signature and no border, '
. 'in any alphabet or language.';
const FLIGHT_CEILING_KG = 16.0;
function check(float $massKg, bool $flies): void {
if ($flies && $massKg > FLIGHT_CEILING_KG) {
throw new InvalidArgumentException("$massKg kg is past the ceiling for powered flight");
}
}
function compile_brief(array $axes, array $order, string $negatives): string {
$parts = array_map(fn($k) => $axes[$k], $order);
$parts[] = $negatives;
return implode(' ', $parts);
}
check(45.0, false);
$brief = compile_brief($AXES, $ORDER, $NEGATIVES);
echo count(preg_split('/\s+/', trim($brief))), " words\n"; // aim for roughly 140
draw($brief);
// Eight axes, one sentence each. Sweep one, hold the others.
using System;
using System.Collections.Generic;
using System.Linq;
public static class Brief
{
static readonly string[] Order = {
"frame", "plate", "plan", "mass", "habitat", "feeding",
"locomotion", "integument", "thermo", "derived"
};
static readonly Dictionary<string, string> Axes = new()
{
["frame"] = "A single plate from a naturalist's field guide to a world that does not exist. One invented animal, drawn as though from a specimen in front of the artist.",
["plate"] = "The plate is a hand-coloured lithograph: fine stone-drawn outline with transparent watercolour laid over it by hand.",
["plan"] = "The animal stands on two hind limbs with a long stiffened tail as a counterweight.",
["mass"] = "It weighs about 45 kg.",
["habitat"] = "It lives on grassland that spends part of every year underwater.",
["feeding"] = "It works through fallen and decaying material.",
["locomotion"] = "It moves in bounds on enlarged hind limbs.",
["integument"] = "It has bare glandular skin with no covering at all.",
["thermo"] = "It keeps its core warm and lets its extremities run near freezing.",
["derived"] = "Those facts settle the rest of it, and the following are to be drawn exactly as given: small blunt uniform teeth, with the real processing happening in the gut; a long capacious gut with a heavy caecum, giving a soft rounded underline; conspicuous thermal windows where heat is dumped."
};
const string Negatives = "There is no lettering anywhere in the picture: no species name, " +
"no caption, no annotation, no numbered key, no scale bar, no signature and no border, " +
"in any alphabet or language.";
const double FlightCeilingKg = 16.0;
static void Check(double massKg, bool flies)
{
if (flies && massKg > FlightCeilingKg)
throw new ArgumentException($"{massKg} kg is past the ceiling for powered flight");
}
static string CompileBrief(Dictionary<string, string> axes) =>
string.Join(" ", Order.Select(k => axes[k]).Append(Negatives));
public static async Task Main()
{
Check(45.0, false);
var brief = CompileBrief(Axes);
Console.WriteLine($"{brief.Split(' ').Length} words"); // aim for roughly 140
await Draw(brief);
}
}
7. Errors and retries
An error is a normal envelope with ok: false. Branch on
error.code, not on the HTTP status — the status is a summary, the code is
the fact.
| HTTP | error.code | Retry? | What to do |
|---|---|---|---|
| 401 | unauthorized | No | The token is missing, malformed or no longer valid. Retrying cannot fix it. Mint a fresh one from tokens.html and check the header really reads Bearer followed by the token. |
| 402 | payment_required | No | The balance is below min_credits, so the hold could not be taken and nothing ran. Top up, or lower your in-flight count — concurrent holds are the usual cause. error.details carries the shortfall. |
| 400 | validation_error | No | The body is malformed. Here that almost always means a missing or mangled $model, or an instruction that is not a string. Fix the body; retrying it unchanged will fail identically. |
| 429 | rate_limited | Yes | Too many requests. Back off exponentially with jitter. Polling faster than every 1.5 seconds is the most common way to land here. |
| 500 / 502 | internal | Yes | The renderer failed. The run is not billed and the hold is released. Retry with the attempt counter bumped, or the idempotency replay will hand you the same dead job back. |
A clean estimate is not proof that a model runs.
/estimate prices a request; it does not execute one. It will happily return a
hold for a body that /run then rejects, and it says nothing about whether the
renderer is healthy, whether the model is currently reachable, or whether this particular
brief will come back refused. Treat it as a budgeting call and nothing more. The first honest
signal that the pipeline works end to end is a job that reaches succeeded with
bytes in images[0].b64, so make that your smoke test, not an estimate.
Two failure modes deserve separate handling. A transport failure —
a dropped connection, a proxy timeout — leaves you not knowing whether the run started.
That is exactly what the idempotency key is for: repeat the request with the same key
and you attach to the original job rather than paying twice. A job failure
— status: "failed" — is a decision the renderer already made, so
repeating it with the same key replays the failure. Bump the attempt counter for that one.
SF_TOKEN="YOUR_TOKEN"
BASE="https://api.skillsafe.ai/v1/app-api"
# Retry only rate_limited and internal. Everything else is a bug in the request.
ATTEMPT=1
while [ "$ATTEMPT" -le 4 ]; do
DIGEST=$(jq -r '.instruction' body.json | shasum -a 256 | cut -c1-16)
RES=$(curl -sS -X POST "$BASE/run" \
-H "Authorization: Bearer $SF_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $DIGEST:attempt-$ATTEMPT" \
--data-binary @body.json)
if [ "$(printf '%s' "$RES" | jq -r '.ok')" = "true" ]; then
printf '%s' "$RES" | jq -r '.data.job_id'
break
fi
CODE=$(printf '%s' "$RES" | jq -r '.error.code')
case "$CODE" in
rate_limited|internal)
# Exponential back-off with jitter: 2s, 4s, 8s, plus 0-1s.
sleep $(( 2 ** ATTEMPT ))
ATTEMPT=$(( ATTEMPT + 1 ))
;;
*)
echo "not retryable: $CODE" >&2
printf '%s' "$RES" | jq '.error'
exit 1
;;
esac
done
import random
import time
RETRYABLE = {"rate_limited", "internal", "transport"}
def paint_with_retries(brief, tries=4):
"""Retry only what is worth retrying, bumping the attempt salt each time."""
for attempt in range(1, tries + 1):
try:
return paint(brief, attempt=attempt)
except AppError as err:
if err.code not in RETRYABLE or attempt == tries:
raise
# Exponential back-off with jitter.
time.sleep(min(2 ** attempt, 30) + random.random())
raise RuntimeError("unreachable")
# unauthorized, payment_required and validation_error are not in RETRYABLE:
# no amount of waiting turns a bad token or a malformed body into a picture.
paint_with_retries(BRIEF)
const RETRYABLE = new Set(["rate_limited", "internal", "transport"]);
/** Retries only what is worth retrying, bumping the attempt salt each time. */
async function paintWithRetries(brief, tries = 4) {
for (let attempt = 1; attempt <= tries; attempt++) {
try {
return await paint(brief, attempt);
} catch (err) {
if (!RETRYABLE.has(err.code) || attempt === tries) throw err;
// Exponential back-off with jitter.
await sleep(Math.min(2 ** attempt, 30) * 1000 + Math.random() * 1000);
}
}
throw new Error("unreachable");
}
// unauthorized, payment_required and validation_error are not retryable:
// no amount of waiting turns a bad token or a malformed body into a picture.
await paintWithRetries(BRIEF);
var retryable = map[string]bool{"rate_limited": true, "internal": true, "transport": true}
// paintWithRetries retries only what is worth retrying, bumping the attempt salt.
func paintWithRetries(brief, path string, tries int) error {
var last error
for attempt := 1; attempt <= tries; attempt++ {
last = paint(brief, path, attempt)
if last == nil {
return nil
}
var ae *apiError
if !errors.As(last, &ae) || !retryable[ae.Code] || attempt == tries {
return last
}
// Exponential back-off with jitter.
back := time.Duration(math.Min(math.Pow(2, float64(attempt)), 30)) * time.Second
time.Sleep(back + time.Duration(rand.Intn(1000))*time.Millisecond)
}
return last
}
// unauthorized, payment_required and validation_error are absent from the map:
// no amount of waiting turns a bad token or a malformed body into a picture.
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
static final Set<String> RETRYABLE = Set.of("rate_limited", "internal", "transport");
/** Retries only what is worth retrying, bumping the attempt salt each time. */
static Path paintWithRetries(String brief, Path out, int tries) throws Exception {
RuntimeException last = null;
for (int attempt = 1; attempt <= tries; attempt++) {
try {
return paint(brief, attempt, out);
} catch (ImpossibleCreatureGenerator.AppException e) {
last = e;
// Read error.code out of the envelope with Jackson or Gson.
String code = codeOf(e.getMessage());
if (!RETRYABLE.contains(code) || attempt == tries) throw e;
// Exponential back-off with jitter.
long back = Math.min(1L << attempt, 30L) * 1000L
+ ThreadLocalRandom.current().nextInt(1000);
Thread.sleep(back);
}
}
throw last;
}
// unauthorized, payment_required and validation_error are not in RETRYABLE:
// no amount of waiting turns a bad token or a malformed body into a picture.
RETRYABLE = %w[rate_limited internal transport].freeze
# Retries only what is worth retrying, bumping the attempt salt each time.
def paint_with_retries(brief, tries = 4)
(1..tries).each do |attempt|
return paint(brief, attempt)
rescue AppError => e
raise if !RETRYABLE.include?(e.code) || attempt == tries
# Exponential back-off with jitter.
sleep([2**attempt, 30].min + rand)
end
end
# unauthorized, payment_required and validation_error are not retryable:
# no amount of waiting turns a bad token or a malformed body into a picture.
paint_with_retries(BRIEF)
<?php
const RETRYABLE = ['rate_limited', 'internal', 'transport'];
/** Retries only what is worth retrying, bumping the attempt salt each time. */
function paint_with_retries(string $brief, int $tries = 4): string {
for ($attempt = 1; $attempt <= $tries; $attempt++) {
try {
return paint($brief, $attempt);
} catch (AppError $e) {
if (!in_array($e->code, RETRYABLE, true) || $attempt === $tries) {
throw $e;
}
// Exponential back-off with jitter.
usleep((int) ((min(2 ** $attempt, 30) + mt_rand(0, 1000) / 1000) * 1_000_000));
}
}
throw new RuntimeException('unreachable');
}
// unauthorized, payment_required and validation_error are not retryable:
// no amount of waiting turns a bad token or a malformed body into a picture.
paint_with_retries($BRIEF);
var retryable = new HashSet<string> { "rate_limited", "internal", "transport" };
var rng = new Random();
// Retries only what is worth retrying, bumping the attempt salt each time.
async Task<string> PaintWithRetries(string brief, int tries = 4)
{
for (var attempt = 1; attempt <= tries; attempt++)
{
try
{
return await Paint(brief, attempt);
}
catch (Exception e)
{
var code = e.Message.Split(':')[0];
if (!retryable.Contains(code) || attempt == tries) throw;
// Exponential back-off with jitter.
var back = Math.Min(Math.Pow(2, attempt), 30) * 1000 + rng.Next(1000);
await Task.Delay((int) back);
}
}
throw new InvalidOperationException("unreachable");
}
// unauthorized, payment_required and validation_error are not retryable:
// no amount of waiting turns a bad token or a malformed body into a picture.
await PaintWithRetries(Brief);
Before you ship
- The run body has two keys:
instructionand$model. Every other key gets painted into the picture. $modelis"gpt-image". Assert onmodel_aliasfrom/estimatethat it survived serialisation.- Signed in is
subject_type === "user"./mehas no email, no name and no id. - The picture is
output.images[0].b64, decoded and written as binary.output.outputis"". - The hold is a reservation, per picture, and it does not move with prompt length. Budget concurrency from
hold_credits; report spend fromcharged_credits. - Poll
/jobs/{id}every 1.5 s, give up at 240 s. There is no SSE worth using on an image run. - Idempotency keys carry a per-attempt salt, so a deliberate retry is a new run and an accidental duplicate is not.
- One operative break per brief; everything else ordinary, plus a named mundane anchor.
- The token lives in a secret store — never a repository, an image, a log line or a browser bundle.