Driving H3 Prompt Studio from code
Base URL https://api.skillsafe.ai/v1/app-api. Every response is
{"ok": true, "data": {…}} or
{"ok": false, "error": {…}}. The app slug is
minimax-h3-prompt.
This app writes plans and prompts. It does not generate video, and the API will not pretend otherwise. The one asset it produces is a preview image, through the platform's image model.
The task field comes first
Everything routes on task. It selects one of nine workflows and, for the seven that
have two stages, which stage. Send it on every call, including /estimate — the
hold differs per task because the prompts and output caps differ. An unrecognised task falls back
to h3-prompt:build and the model says so in its Summary.
| task | Workflow | Stage | Required fields | Optional fields |
|---|---|---|---|---|
route | Suggest a workflow | Suggest | brief | - |
h3-prompt:build | H3 prompt writing | Write the prompt | brief, mode, seconds | refs |
handdrawn:build | Hand-drawn / live-action fusion | Write the prompt | brief | contact, language |
co-op-intro:plan | Co-op game intro | 1. Confirmation image | brief, style | refs |
co-op-intro:build | Co-op game intro | 2. Video prompt | approved | changes |
product-ad:plan | Minimalist product ad | 1. Brief and copy | brief, template, copy_mode | duration, aspect, copy |
product-ad:build | Minimalist product ad | 2. Anchors and beats | approved | beats |
brand-promo:plan | Brand promo video | 1. Truth sheet and beats | brief | duration, aspect, audience, assets_held |
brand-promo:build | Brand promo video | 2. Production package | approved | audio |
mv-subtitle:plan | Music video with lyric typography | 1. Pre-flight lock | brief | lyrics, duration, aspect, window |
mv-subtitle:build | Music video with lyric typography | 2. Multi-shot script | approved | preset |
paper-collage:plan | Paper collage explainer | 1. Gate 1 - plan | brief | duration, aspect, segments, addons |
paper-collage:build | Paper collage explainer | 2. Stills and clips | approved | palette |
papercraft:plan | Papercraft stop-motion explainer | 1. Design and previews | brief, deliverable | audience, duration, aspect |
papercraft:build | Papercraft stop-motion explainer | 2. Prompts and storyboard | approved | narration |
anim-short:plan | 3D animated short | 1. Story and cards | brief | duration, aspect, dialogue, style |
anim-short:table | 3D animated short | 2. Shot table | approved | shots |
anim-short:build | 3D animated short | 3. Boards and assembly | approved | boards |
Two fields you always send
task selects the contract. rules carries the working rules for that one
stage, which the app keeps in h3rules.js rather than in the
system prompt — so a run pays for the rules of the stage it is running and no others. Fetch
that file, read H3Rules.forTask(task), and pass the string through.
Two optional fields are JSON-encoded strings rather than objects, because input-schema field types
are scalar-only: prescan (what the client-side validator or workflow scorer already
found, which the model is required to respond to) and clipped (a list of fields that
were trimmed before the model saw them).
Choosing the model
Pass $model with any id from GET https://api.skillsafe.ai/v1/models,
which is public and needs no token. Drop anything reporting
available: false. Text models write packages; the image model renders previews and
takes a body of exactly {"instruction", "$model"} — every extra key is
concatenated into the text the image model sees. Omit $model and the run uses the
app's configured model, gpt-terra.
Errors
| error.code | HTTP | What it means |
|---|---|---|
validation_error | 400 | The body is not an object, or a field is the wrong type. With an input schema declared, warnings[] names the offending field. The {"input": {...}} wrapper lands here. |
unauthorized | 401 | Missing or expired token. Mint a guest token, or sign in for a personal one. |
forbidden | 403 | A guest token tried to run. Runs need a signed-in subject. |
not_found | 404 | Unknown job id, or a file id minted under a different subject. |
insufficient_credits | 402 | Balance is below min_credits. Compare against hold_credits before you submit, not after. |
rate_limited | 429 | Back off. Similarity search is 30/min per IP; the other endpoints are more generous. |
internal | 500 | The model run failed. Failed runs are not billed. |
Step by step
1. A tiny client
Every call goes to the app API with a Bearer token and a JSON body. The envelope is always {"ok": true, "data": {...}} or {"ok": false, "error": {"code", "message", "status", "details"}}.
# Every call is Bearer-authenticated against the app API.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="YOUR_TOKEN"
call() { # call METHOD PATH [JSON]
curl -sS -X "$1" "$BASE$2" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
${3:+-d "$3"}
}import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN"
def call(method, path, body=None, headers=None):
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(BASE + path, data=data, method=method)
req.add_header("Authorization", "Bearer " + TOKEN)
req.add_header("Content-Type", "application/json")
for k, v in (headers or {}).items():
req.add_header(k, v)
with urllib.request.urlopen(req) as r:
return json.loads(r.read())const BASE = "https://api.skillsafe.ai/v1/app-api";
const TOKEN = "YOUR_TOKEN";
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 json = await res.json();
if (!json.ok) throw new Error(json.error.code + ": " + json.error.message);
return json.data;
}package main
import (
"bytes"
"encoding/json"
"io"
"net/http"
)
const base = "https://api.skillsafe.ai/v1/app-api"
const token = "YOUR_TOKEN"
func call(method, path string, body any) (map[string]any, error) {
var rdr io.Reader
if body != nil {
b, _ := json.Marshal(body)
rdr = bytes.NewReader(b)
}
req, _ := http.NewRequest(method, base+path, rdr)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
var out map[string]any
return out, json.NewDecoder(res.Body).Decode(&out)
}import java.net.URI;
import java.net.http.*;
class Client {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static final String TOKEN = "YOUR_TOKEN";
static final HttpClient HTTP = HttpClient.newHttpClient();
static String call(String method, String path, String body) throws Exception {
HttpRequest.BodyPublisher pub = body == null
? HttpRequest.BodyPublishers.noBody()
: HttpRequest.BodyPublishers.ofString(body);
HttpRequest req = HttpRequest.newBuilder(URI.create(BASE + path))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.method(method, pub)
.build();
return HTTP.send(req, HttpResponse.BodyHandlers.ofString()).body();
}
}require "json"
require "net/http"
require "uri"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN"
def call(method, path, body = nil)
uri = URI(BASE + path)
klass = Net::HTTP.const_get(method.capitalize)
req = klass.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req.body = JSON.generate(body) if body
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
JSON.parse(res.body)
end<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
const TOKEN = "YOUR_TOKEN";
function call(string $method, string $path, ?array $body = null): array {
$ch = curl_init(BASE . $path);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer " . TOKEN,
"Content-Type: application/json",
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
$out = curl_exec($ch);
curl_close($ch);
return json_decode($out, true);
}using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
static class Client {
const string Base = "https://api.skillsafe.ai/v1/app-api";
const string Token = "YOUR_TOKEN";
static readonly HttpClient Http = new HttpClient();
public static async Task<JsonDocument> Call(HttpMethod method, string path, object body = null) {
var req = new HttpRequestMessage(method, Base + path);
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Token);
if (body != null)
req.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
var res = await Http.SendAsync(req);
return JsonDocument.Parse(await res.Content.ReadAsStringAsync());
}
}2. Get a token
A guest token is minted per app and is enough for /me and /estimate. Writing a package is metered and needs a personal token: the token page shows yours and copies a shell export, so you never have to open the developer console.
# A guest token is enough for /me and /estimate. Writing a package needs a
# personal token: open https://minimax-h3-prompt.skillsafe.ai/tokens.html and
# use "Copy shell export".
curl -sS -X POST "https://api.skillsafe.ai/v1/app-api/guest" \
-H "Content-Type: application/json" \
-d '{"slug":"minimax-h3-prompt"}' # Guest token: enough for /me and /estimate.
guest = call("POST", "/guest", {"slug": "minimax-h3-prompt"})
TOKEN = guest["data"]["token"]
# For a metered run use a personal token from
# https://minimax-h3-prompt.skillsafe.ai/tokens.html// Guest token: enough for /me and /estimate.
const guest = await call("POST", "/guest", { slug: "minimax-h3-prompt" });
// For a metered run use a personal token from
// https://minimax-h3-prompt.skillsafe.ai/tokens.html// Guest token: enough for /me and /estimate.
guest, _ := call("POST", "/guest", map[string]string{"slug": "minimax-h3-prompt"})
// For a metered run use a personal token from
// https://minimax-h3-prompt.skillsafe.ai/tokens.html// Guest token: enough for /me and /estimate.
String guest = Client.call("POST", "/guest", "{\"slug\":\"minimax-h3-prompt\"}");
// For a metered run use a personal token from
// https://minimax-h3-prompt.skillsafe.ai/tokens.html# Guest token: enough for /me and /estimate.
guest = call("post", "/guest", { "slug" => "minimax-h3-prompt" })
# For a metered run use a personal token from
# https://minimax-h3-prompt.skillsafe.ai/tokens.html<?php
// Guest token: enough for /me and /estimate.
$guest = call("POST", "/guest", ["slug" => "minimax-h3-prompt"]);
// For a metered run use a personal token from
// https://minimax-h3-prompt.skillsafe.ai/tokens.html// Guest token: enough for /me and /estimate.
var guest = await Client.Call(HttpMethod.Post, "/guest", new { slug = "minimax-h3-prompt" });
// For a metered run use a personal token from
// https://minimax-h3-prompt.skillsafe.ai/tokens.html3. Check the session
/me returns only three fields: subject_type, subject_id and credits. Signed in means subject_type === "user".
curl -sS "https://api.skillsafe.ai/v1/app-api/me" -H "Authorization: Bearer $TOKEN"
# {"ok":true,"data":{"subject_type":"user","subject_id":"usr_...","credits":124500}}
# subject_type is "user" when signed in and "guest" otherwise. Guests cannot run.me = call("GET", "/me")["data"]
print(me["subject_type"], me["credits"])
# subject_type is "user" when signed in and "guest" otherwise. Guests cannot run.const me = await call("GET", "/me");
console.log(me.subject_type, me.credits);
// subject_type is "user" when signed in and "guest" otherwise. Guests cannot run.me, _ := call("GET", "/me", nil)
fmt.Println(me["data"])
// subject_type is "user" when signed in and "guest" otherwise. Guests cannot run.String me = Client.call("GET", "/me", null);
System.out.println(me);
// subject_type is "user" when signed in and "guest" otherwise. Guests cannot run.me = call("get", "/me")["data"]
puts me["subject_type"], me["credits"]
# subject_type is "user" when signed in and "guest" otherwise. Guests cannot run.<?php
$me = call("GET", "/me")["data"];
echo $me["subject_type"], " ", $me["credits"];
// subject_type is "user" when signed in and "guest" otherwise. Guests cannot run.var me = await Client.Call(HttpMethod.Get, "/me");
Console.WriteLine(me.RootElement.GetProperty("data"));
// subject_type is "user" when signed in and "guest" otherwise. Guests cannot run.4. Estimate, free
/estimate creates no job and costs nothing. It reports the hold, the resolved model and the markup, and validates the body.
curl -sS -X POST "https://api.skillsafe.ai/v1/app-api/estimate" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"task": "h3-prompt:build", "rules": "<the rules for this stage, from GET /h3rules.js>", "brief": "A woman at a workshop bench fits a brass hand to a clock face.", "mode": "I2VA", "seconds": 8, "refs": "Picture 1: the woman at the bench", "$model": "gpt-5.6-terra"}'
# Free, and it does not create a job. Returns hold_credits (a RESERVATION, not a
# price), min_credits, model, model_alias, markup_bps, and - when the app
# declares an input schema - input_checked plus warnings[] naming any unknown or
# mistyped field. Warnings are advisory: the run still happens and is charged.est = call("POST", "/estimate", {
"task": "h3-prompt:build",
"rules": "<the rules for this stage, from GET /h3rules.js>",
"brief": "A woman at a workshop bench fits a brass hand to a clock face.",
"mode": "I2VA",
"seconds": 8,
"refs": "Picture 1: the woman at the bench",
"$model": "gpt-5.6-terra"
})["data"]
print(est["hold_credits"], est["model"], est.get("warnings"))
# Free, and it creates no job. hold_credits is a RESERVATION, not a price.const est = await call("POST", "/estimate", {
"task": "h3-prompt:build",
"rules": "<the rules for this stage, from GET /h3rules.js>",
"brief": "A woman at a workshop bench fits a brass hand to a clock face.",
"mode": "I2VA",
"seconds": 8,
"refs": "Picture 1: the woman at the bench",
"$model": "gpt-5.6-terra"
});
console.log(est.hold_credits, est.model, est.warnings);
// Free, and it creates no job. hold_credits is a RESERVATION, not a price.est, _ := call("POST", "/estimate", map[string]any{
"task": "h3-prompt:build",
"rules": rulesForTask,
"brief": "A woman at a workshop bench fits a brass hand to a clock face.",
"mode": "I2VA",
"seconds": 8,
"$model": "gpt-5.6-terra",
})
// Free, and it creates no job. hold_credits is a RESERVATION, not a price.String est = Client.call("POST", "/estimate", """
{
"task": "h3-prompt:build",
"rules": "<the rules for this stage, from GET /h3rules.js>",
"brief": "A woman at a workshop bench fits a brass hand to a clock face.",
"mode": "I2VA",
"seconds": 8,
"refs": "Picture 1: the woman at the bench",
"$model": "gpt-5.6-terra"
}""");
// Free, and it creates no job. hold_credits is a RESERVATION, not a price.est = call("post", "/estimate", {
"task" => "h3-prompt:build",
"rules" => rules_for_task,
"brief" => "A woman at a workshop bench fits a brass hand to a clock face.",
"mode" => "I2VA",
"seconds" => 8,
"$model" => "gpt-5.6-terra"
})["data"]
# Free, and it creates no job. hold_credits is a RESERVATION, not a price.<?php
$est = call("POST", "/estimate", [
"task" => "h3-prompt:build",
"rules" => $rulesForTask,
"brief" => "A woman at a workshop bench fits a brass hand to a clock face.",
"mode" => "I2VA",
"seconds" => 8,
"$model" => "gpt-5.6-terra",
])["data"];
// Free, and it creates no job. hold_credits is a RESERVATION, not a price.var est = await Client.Call(HttpMethod.Post, "/estimate", new Dictionary<string, object> {
["task"] = "h3-prompt:build",
["rules"] = rulesForTask,
["brief"] = "A woman at a workshop bench fits a brass hand to a clock face.",
["mode"] = "I2VA",
["seconds"] = 8,
["$model"] = "gpt-5.6-terra",
});
// Free, and it creates no job. hold_credits is a RESERVATION, not a price.5. Run and poll
/run returns a job_id immediately. Poll /jobs/{id} until the status is terminal.
# Always send Idempotency-Key. Derive it from the task plus the input, so a
# retried request never double-bills and two tasks over one brief never collide.
JOB=$(curl -sS -X POST "https://api.skillsafe.ai/v1/app-api/run" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: h3-h3promptbuild-9f2c1a" \
-d '{"task": "h3-prompt:build", "rules": "<the rules for this stage, from GET /h3rules.js>", "brief": "A woman at a workshop bench fits a brass hand to a clock face.", "mode": "I2VA", "seconds": 8, "refs": "Picture 1: the woman at the bench", "$model": "gpt-5.6-terra"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["data"]["job_id"])')
# poll to a terminal state
curl -sS "https://api.skillsafe.ai/v1/app-api/jobs/$JOB" -H "Authorization: Bearer $TOKEN"import time
started = call("POST", "/run", {
"task": "h3-prompt:build",
"rules": "<the rules for this stage, from GET /h3rules.js>",
"brief": "A woman at a workshop bench fits a brass hand to a clock face.",
"mode": "I2VA",
"seconds": 8,
"refs": "Picture 1: the woman at the bench",
"$model": "gpt-5.6-terra"
},
headers={"Idempotency-Key": "h3-h3promptbuild-9f2c1a"})["data"]
while True:
job = call("GET", "/jobs/" + started["job_id"])["data"]
if job["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(2)
print(job["output"]["output"]) # the Markdown package
print(job["charged_credits"]) # the ACTUAL cost, usually well under the holdconst started = await call("POST", "/run", {
"task": "h3-prompt:build",
"rules": "<the rules for this stage, from GET /h3rules.js>",
"brief": "A woman at a workshop bench fits a brass hand to a clock face.",
"mode": "I2VA",
"seconds": 8,
"refs": "Picture 1: the woman at the bench",
"$model": "gpt-5.6-terra"
},
{ "Idempotency-Key": "h3-h3promptbuild-9f2c1a" });
let job;
do {
await new Promise(r => setTimeout(r, 2000));
job = await call("GET", `/jobs/${started.job_id}`);
} while (!["succeeded", "failed", "cancelled"].includes(job.status));
console.log(job.output.output); // the Markdown package
console.log(job.charged_credits); // the ACTUAL cost, usually well under the holdstarted, _ := call("POST", "/run", body) // + header Idempotency-Key
id := started["data"].(map[string]any)["job_id"].(string)
for {
job, _ := call("GET", "/jobs/"+id, nil)
st := job["data"].(map[string]any)["status"].(string)
if st == "succeeded" || st == "failed" || st == "cancelled" {
break
}
time.Sleep(2 * time.Second)
}String started = Client.call("POST", "/run", body); // + header Idempotency-Key
// then GET /jobs/{job_id} every two seconds until status is
// succeeded, failed or cancelled.started = call("post", "/run", body)["data"] # + header Idempotency-Key
loop do
job = call("get", "/jobs/#{started['job_id']}")["data"]
break if %%w[succeeded failed cancelled].include?(job["status"])
sleep 2
end<?php
$started = call("POST", "/run", $body)["data"]; // + header Idempotency-Key
do {
sleep(2);
$job = call("GET", "/jobs/" . $started["job_id"])["data"];
} while (!in_array($job["status"], ["succeeded", "failed", "cancelled"]));
echo $job["output"]["output"];var started = await Client.Call(HttpMethod.Post, "/run", body); // + Idempotency-Key
// then GET /jobs/{job_id} every two seconds until status is
// succeeded, failed or cancelled.6. Or stream it
/run-stream sends server-sent events. The delta frame carries text, not an object — accumulating frame.text is what gets you the output as it arrives.
curl -sSN -X POST "https://api.skillsafe.ai/v1/app-api/run-stream" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: h3-h3promptbuild-9f2c1a" \
-d '{"task": "h3-prompt:build", "rules": "<the rules for this stage, from GET /h3rules.js>", "brief": "A woman at a workshop bench fits a brass hand to a clock face.", "mode": "I2VA", "seconds": 8, "refs": "Picture 1: the woman at the bench", "$model": "gpt-5.6-terra"}'
# Server-sent events. `delta` frames carry {"text":"..."} - the incremental
# output. `job` frames carry the job object. `done` carries the terminal job.
# Accumulate the delta text: that is the package, arriving section by section.req = urllib.request.Request(BASE + "/run-stream",
data=json.dumps(body).encode(), method="POST")
req.add_header("Authorization", "Bearer " + TOKEN)
req.add_header("Content-Type", "application/json")
req.add_header("Idempotency-Key", "h3-h3promptbuild-9f2c1a")
buf = ""
with urllib.request.urlopen(req) as r:
for raw in r:
line = raw.decode().strip()
if not line.startswith("data:"):
continue
frame = json.loads(line[5:].strip())
if "text" in frame:
buf += frame["text"]
# sections arrive in contract order; "## " marks each one startingconst res = await fetch(BASE + "/run-stream", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json",
"Idempotency-Key": "h3-h3promptbuild-9f2c1a"
},
body: JSON.stringify(body)
});
const reader = res.body.getReader();
const dec = new TextDecoder();
let buf = "";
for (;;) {
const { value, done } = await reader.read();
if (done) break;
for (const line of dec.decode(value).split("\n")) {
if (!line.startsWith("data:")) continue;
const frame = JSON.parse(line.slice(5).trim());
if (frame.text) buf += frame.text; // NOTE: the delta is frame.text
}
}// POST /run-stream with the same body and an Idempotency-Key header, then read
// the response body line by line. Lines beginning "data:" carry a JSON frame;
// frames with a "text" key are output deltas to accumulate.// POST /run-stream with the same body and an Idempotency-Key header, then read
// the response as a line stream. Lines beginning "data:" carry a JSON frame;
// frames with a "text" key are output deltas to accumulate.# POST /run-stream with the same body and an Idempotency-Key header, reading the
# response in chunks. Lines beginning "data:" carry a JSON frame; frames with a
# "text" key are output deltas to accumulate.<?php
// POST /run-stream with the same body and an Idempotency-Key header, using
// CURLOPT_WRITEFUNCTION to read frames as they arrive. Lines beginning "data:"
// carry a JSON frame; frames with a "text" key are output deltas.// POST /run-stream with the same body and an Idempotency-Key header, then read
// the response stream line by line. Lines beginning "data:" carry a JSON frame;
// frames with a "text" key are output deltas to accumulate.Every task contract
The output is Markdown whose ## headings are fixed per task, in order. Parse by
heading; a missing heading is a section that did not arrive, which is worth surfacing rather than
silently dropping.
route — Suggest a workflow, Suggest
A ranked recommendation of which of the nine workflows fits your request. Source skill: .
Minimal body
{
"task": "route",
"rules": "<rules for this stage>",
"brief": "<your brief>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Recommended Workflow## Why This One## Runners Up## Out Of Scope## Prefilled Brief## Next Step
h3-prompt:build — H3 prompt writing, Write the prompt
A MiniMax H3 prompt in the exact published structure, for any of the five generation modes. Source skill: h3-prompt-writing.
Minimal body
{
"task": "h3-prompt:build",
"rules": "<rules for this stage>",
"brief": "<your brief>",
"mode": "<your mode>",
"seconds": 8,
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Mode## H3 Prompt## Field Check## Reconciliation## Next Step
handdrawn:build — Hand-drawn / live-action fusion, Write the prompt
A reusable 15-second 16:9 prompt for glowing hand-drawn animation touching a real space. Source skill: handdrawn-live-video-generator.
Minimal body
{
"task": "handdrawn:build",
"rules": "<rules for this stage>",
"brief": "<your brief>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Video Prompt## Paragraph Order Check## Invention Log## Recommendation## Next Step
co-op-intro:plan — Co-op game intro, 1. Confirmation image
A confirmation-image prompt, then the H3 video prompt built from the image you approved. Source skill: co-op-game-intro-generator.
Minimal body
{
"task": "co-op-intro:plan",
"rules": "<rules for this stage>",
"brief": "<your brief>",
"style": "<your style>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Style Lock## Player And Game Data## Identity Anchors## Confirmation Image Prompt## Template Field Check## Next Step
co-op-intro:build — Co-op game intro, 2. Video prompt
A confirmation-image prompt, then the H3 video prompt built from the image you approved. Source skill: co-op-game-intro-generator.
Minimal body
{
"task": "co-op-intro:build",
"rules": "<rules for this stage>",
"approved": "<your approved>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Approved Image Recap## Video Prompt## UI Copy And Event Timing## Negative Constraints## Failure Repairs## Next Step
product-ad:plan — Minimalist product ad, 1. Brief and copy
An Apple-style product film plan: fact summary, spine, copy, anchor photos and a beat storyboard. Source skill: minimalist-product-ad-generator.
Minimal body
{
"task": "product-ad:plan",
"rules": "<rules for this stage>",
"brief": "<your brief>",
"template": "<your template>",
"copy_mode": "<your copy_mode>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Start Gate Result## Product Fact Summary## Production Brief## Narrative Spine## Motion Language## Copy Options## Next Step
product-ad:build — Minimalist product ad, 2. Anchors and beats
An Apple-style product film plan: fact summary, spine, copy, anchor photos and a beat storyboard. Source skill: minimalist-product-ad-generator.
Minimal body
{
"task": "product-ad:build",
"rules": "<rules for this stage>",
"approved": "<your approved>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Anchor Photo Prompts## User Choice Statement## Beat Content Table## Principles Check## Video Prompt## BGM Direction## Delivery Verification## Next Step
brand-promo:plan — Brand promo video, 1. Truth sheet and beats
A verified brand truth sheet, a provenance manifest and a frame-aware beat plan. Source skill: brand-promo-video-generator.
Minimal body
{
"task": "brand-promo:plan",
"rules": "<rules for this stage>",
"brief": "<your brief>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Intake And Brief## Brand Truth Sheet## Provenance Manifest## Story Spine Options## Beat Plan## Motion Language## Caveats## Next Step
brand-promo:build — Brand promo video, 2. Production package
A verified brand truth sheet, a provenance manifest and a frame-aware beat plan. Source skill: brand-promo-video-generator.
Minimal body
{
"task": "brand-promo:build",
"rules": "<rules for this stage>",
"approved": "<your approved>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Production Dispatches## Shot Prompts## Audio Plan## Pre-Delivery Checks## Delivery Package## Failure Recovery## Next Step
mv-subtitle:plan — Music video with lyric typography, 1. Pre-flight lock
A pre-flight lock, locked lyrics, and a modular multi-shot MV script with beat-reactive text. Source skill: music-video-subtitle-generator.
Minimal body
{
"task": "mv-subtitle:plan",
"rules": "<rules for this stage>",
"brief": "<your brief>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Pre-flight Lock## Locked Lyrics## Creative Contract## Reference Roles## Shotlist Timeline## Next Step
mv-subtitle:build — Music video with lyric typography, 2. Multi-shot script
A pre-flight lock, locked lyrics, and a modular multi-shot MV script with beat-reactive text. Source skill: music-video-subtitle-generator.
Minimal body
{
"task": "mv-subtitle:build",
"rules": "<rules for this stage>",
"approved": "<your approved>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Global Aesthetic And Character Lock## Multi-Shot Script## Typography Plan## Stitching Protocol## Checklist## Next Step
paper-collage:plan — Paper collage explainer, 1. Gate 1 - plan
A production plan, still-frame specs and stop-motion assembly prompts in halftone collage language. Source skill: paper-collage-explainer-generator.
Minimal body
{
"task": "paper-collage:plan",
"rules": "<rules for this stage>",
"brief": "<your brief>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Brief## Visual Metaphors## Script Or Visual Beat Track## Storyboard## Media Approach## Next Step
paper-collage:build — Paper collage explainer, 2. Stills and clips
A production plan, still-frame specs and stop-motion assembly prompts in halftone collage language. Source skill: paper-collage-explainer-generator.
Minimal body
{
"task": "paper-collage:build",
"rules": "<rules for this stage>",
"approved": "<your approved>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Still Frame Specifications## Still Image Prompts## Stop-Motion Assembly Plan## Video Prompts## Quality Review## Assembly And Delivery## Next Step
papercraft:plan — Papercraft stop-motion explainer, 1. Design and previews
A production-ready papercraft package: style DNA, characters, diorama staging, prompts and storyboard. Source skill: papercraft-stop-motion-explainer.
Minimal body
{
"task": "papercraft:plan",
"rules": "<rules for this stage>",
"brief": "<your brief>",
"deliverable": "<your deliverable>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Understanding Block## Style DNA## Creative Directions## Paper Characters## Paper Scenes## Layered Diorama Staging## Prop And Asset Library## Preview Image Prompts## Next Step
papercraft:build — Papercraft stop-motion explainer, 2. Prompts and storyboard
A production-ready papercraft package: style DNA, characters, diorama staging, prompts and storyboard. Source skill: papercraft-stop-motion-explainer.
Minimal body
{
"task": "papercraft:build",
"rules": "<rules for this stage>",
"approved": "<your approved>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Single Image Prompt## Image Series Prompts## Image To Video Prompt## Storyboard## Editing Rhythm## Camera Rules## Transitions## Sound Design## Negative Prompts## Review Checklist## Next Step
anim-short:plan — 3D animated short, 1. Story and cards
A brief, story outline, character and scene cards, then the six-column shot table and storyboard. Source skill: 3d-animation-short-generator.
Minimal body
{
"task": "anim-short:plan",
"rules": "<rules for this stage>",
"brief": "<your brief>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Project Brief## Story Outline## Gate Checks## Character Cards## Scene Cards## Next Step
anim-short:table — 3D animated short, 2. Shot table
A brief, story outline, character and scene cards, then the six-column shot table and storyboard. Source skill: 3d-animation-short-generator.
Minimal body
{
"task": "anim-short:table",
"rules": "<rules for this stage>",
"approved": "<your approved>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Standard Shot Table## Shot Table Self-Check## Next Step
anim-short:build — 3D animated short, 3. Boards and assembly
A brief, story outline, character and scene cards, then the six-column shot table and storyboard. Source skill: 3d-animation-short-generator.
Minimal body
{
"task": "anim-short:build",
"rules": "<rules for this stage>",
"approved": "<your approved>",
"$model": "gpt-5.6-terra"
}Output sections, in this order
## Summary## Text Storyboards## Model Selection## Fallback Ladders## Assembly And BGM## Final Review## Next Step
The H3 prompt structure
The h3-prompt:build task emits a prompt in one fenced text block. Base
modes give a mode-specific alignment line, a blank line, then
integrated_multimodal_description, overall_soundscape and
non_diegetic_music. Ref2VA gives subject_definitions,
summary, retention_analysis, detailed_description,
overall_soundscape and non_diegetic_music.
The same validator the page runs is in h3lint.js and works
standalone: H3Lint.check(text, {mode, seconds, declaredRefs}) returns
{ok, findings, shots, speakers, labels, motions}. It needs no token and makes no
network call, so it is worth running over anything you generate before you send it to H3.