curl --request POST \
--url https://api.aurous-labs.com/v1/characters \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: <api-key>' \
--data '
{
"name": "Aurora the Adventurer",
"generate": false,
"upload_ids": [
"upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR"
],
"attributes": {
"gender": "female",
"age": 28,
"ethnicity": "east-asian",
"hair_color": "black",
"hair_style": "shoulder-length straight",
"eye_color": "brown",
"body_type": "athletic",
"additional_details": "small scar above right eyebrow; warm smile"
},
"nudity": "clothed",
"client_reference_id": "bot_8472"
}
'import requests
url = "https://api.aurous-labs.com/v1/characters"
payload = {
"name": "Aurora the Adventurer",
"generate": False,
"upload_ids": ["upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR"],
"attributes": {
"gender": "female",
"age": 28,
"ethnicity": "east-asian",
"hair_color": "black",
"hair_style": "shoulder-length straight",
"eye_color": "brown",
"body_type": "athletic",
"additional_details": "small scar above right eyebrow; warm smile"
},
"nudity": "clothed",
"client_reference_id": "bot_8472"
}
headers = {
"X-Api-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-Api-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Aurora the Adventurer',
generate: false,
upload_ids: ['upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR'],
attributes: {
gender: 'female',
age: 28,
ethnicity: 'east-asian',
hair_color: 'black',
hair_style: 'shoulder-length straight',
eye_color: 'brown',
body_type: 'athletic',
additional_details: 'small scar above right eyebrow; warm smile'
},
nudity: 'clothed',
client_reference_id: 'bot_8472'
})
};
fetch('https://api.aurous-labs.com/v1/characters', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.aurous-labs.com/v1/characters",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Aurora the Adventurer',
'generate' => false,
'upload_ids' => [
'upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR'
],
'attributes' => [
'gender' => 'female',
'age' => 28,
'ethnicity' => 'east-asian',
'hair_color' => 'black',
'hair_style' => 'shoulder-length straight',
'eye_color' => 'brown',
'body_type' => 'athletic',
'additional_details' => 'small scar above right eyebrow; warm smile'
],
'nudity' => 'clothed',
'client_reference_id' => 'bot_8472'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-Api-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.aurous-labs.com/v1/characters"
payload := strings.NewReader("{\n \"name\": \"Aurora the Adventurer\",\n \"generate\": false,\n \"upload_ids\": [\n \"upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR\"\n ],\n \"attributes\": {\n \"gender\": \"female\",\n \"age\": 28,\n \"ethnicity\": \"east-asian\",\n \"hair_color\": \"black\",\n \"hair_style\": \"shoulder-length straight\",\n \"eye_color\": \"brown\",\n \"body_type\": \"athletic\",\n \"additional_details\": \"small scar above right eyebrow; warm smile\"\n },\n \"nudity\": \"clothed\",\n \"client_reference_id\": \"bot_8472\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Api-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.aurous-labs.com/v1/characters")
.header("X-Api-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Aurora the Adventurer\",\n \"generate\": false,\n \"upload_ids\": [\n \"upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR\"\n ],\n \"attributes\": {\n \"gender\": \"female\",\n \"age\": 28,\n \"ethnicity\": \"east-asian\",\n \"hair_color\": \"black\",\n \"hair_style\": \"shoulder-length straight\",\n \"eye_color\": \"brown\",\n \"body_type\": \"athletic\",\n \"additional_details\": \"small scar above right eyebrow; warm smile\"\n },\n \"nudity\": \"clothed\",\n \"client_reference_id\": \"bot_8472\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.aurous-labs.com/v1/characters")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Api-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Aurora the Adventurer\",\n \"generate\": false,\n \"upload_ids\": [\n \"upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR\"\n ],\n \"attributes\": {\n \"gender\": \"female\",\n \"age\": 28,\n \"ethnicity\": \"east-asian\",\n \"hair_color\": \"black\",\n \"hair_style\": \"shoulder-length straight\",\n \"eye_color\": \"brown\",\n \"body_type\": \"athletic\",\n \"additional_details\": \"small scar above right eyebrow; warm smile\"\n },\n \"nudity\": \"clothed\",\n \"client_reference_id\": \"bot_8472\"\n}"
response = http.request(request)
puts response.read_body{
"id": "char_01HXMQ7Z3K8Y2NABCDEFGHJKMR",
"object": "character",
"name": "Aurora the Adventurer",
"status": "ready",
"nudity": "clothed",
"refs": [
{
"pose": "front",
"view": "full_front",
"source": "generated",
"url": "https://storage-host.example/storage/v1/object/sign/characters/full_front.jpg?token=eyJhbGciOi..."
}
],
"views": [
{
"view": "full_front",
"status": "generated",
"url": null,
"nudity": "clothed",
"reason": null,
"detected_view": null,
"made_from": [
"head_front",
"full_front"
]
}
],
"build": {
"missing_views": [
"full_front",
"full_right"
],
"cost_credits": 123,
"per_view_credits": 123
},
"created_at": "2026-05-08T10:00:00Z",
"updated_at": "2026-05-08T10:00:00Z",
"client_reference_id": "bot_8472",
"attributes": {
"gender": "female",
"age": 28,
"ethnicity": "east-asian",
"hair_color": "black",
"hair_style": "shoulder-length straight",
"eye_color": "brown",
"body_type": "athletic",
"additional_details": "small scar above right eyebrow; warm smile"
},
"error_message": null,
"aurous_version": "2026-08-26"
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}Create a character
Upload existing reference photos or synthesize a character; eight named views are rendered either way.
curl --request POST \
--url https://api.aurous-labs.com/v1/characters \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: <api-key>' \
--data '
{
"name": "Aurora the Adventurer",
"generate": false,
"upload_ids": [
"upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR"
],
"attributes": {
"gender": "female",
"age": 28,
"ethnicity": "east-asian",
"hair_color": "black",
"hair_style": "shoulder-length straight",
"eye_color": "brown",
"body_type": "athletic",
"additional_details": "small scar above right eyebrow; warm smile"
},
"nudity": "clothed",
"client_reference_id": "bot_8472"
}
'import requests
url = "https://api.aurous-labs.com/v1/characters"
payload = {
"name": "Aurora the Adventurer",
"generate": False,
"upload_ids": ["upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR"],
"attributes": {
"gender": "female",
"age": 28,
"ethnicity": "east-asian",
"hair_color": "black",
"hair_style": "shoulder-length straight",
"eye_color": "brown",
"body_type": "athletic",
"additional_details": "small scar above right eyebrow; warm smile"
},
"nudity": "clothed",
"client_reference_id": "bot_8472"
}
headers = {
"X-Api-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-Api-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Aurora the Adventurer',
generate: false,
upload_ids: ['upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR'],
attributes: {
gender: 'female',
age: 28,
ethnicity: 'east-asian',
hair_color: 'black',
hair_style: 'shoulder-length straight',
eye_color: 'brown',
body_type: 'athletic',
additional_details: 'small scar above right eyebrow; warm smile'
},
nudity: 'clothed',
client_reference_id: 'bot_8472'
})
};
fetch('https://api.aurous-labs.com/v1/characters', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.aurous-labs.com/v1/characters",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Aurora the Adventurer',
'generate' => false,
'upload_ids' => [
'upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR'
],
'attributes' => [
'gender' => 'female',
'age' => 28,
'ethnicity' => 'east-asian',
'hair_color' => 'black',
'hair_style' => 'shoulder-length straight',
'eye_color' => 'brown',
'body_type' => 'athletic',
'additional_details' => 'small scar above right eyebrow; warm smile'
],
'nudity' => 'clothed',
'client_reference_id' => 'bot_8472'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-Api-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.aurous-labs.com/v1/characters"
payload := strings.NewReader("{\n \"name\": \"Aurora the Adventurer\",\n \"generate\": false,\n \"upload_ids\": [\n \"upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR\"\n ],\n \"attributes\": {\n \"gender\": \"female\",\n \"age\": 28,\n \"ethnicity\": \"east-asian\",\n \"hair_color\": \"black\",\n \"hair_style\": \"shoulder-length straight\",\n \"eye_color\": \"brown\",\n \"body_type\": \"athletic\",\n \"additional_details\": \"small scar above right eyebrow; warm smile\"\n },\n \"nudity\": \"clothed\",\n \"client_reference_id\": \"bot_8472\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Api-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.aurous-labs.com/v1/characters")
.header("X-Api-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Aurora the Adventurer\",\n \"generate\": false,\n \"upload_ids\": [\n \"upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR\"\n ],\n \"attributes\": {\n \"gender\": \"female\",\n \"age\": 28,\n \"ethnicity\": \"east-asian\",\n \"hair_color\": \"black\",\n \"hair_style\": \"shoulder-length straight\",\n \"eye_color\": \"brown\",\n \"body_type\": \"athletic\",\n \"additional_details\": \"small scar above right eyebrow; warm smile\"\n },\n \"nudity\": \"clothed\",\n \"client_reference_id\": \"bot_8472\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.aurous-labs.com/v1/characters")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Api-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Aurora the Adventurer\",\n \"generate\": false,\n \"upload_ids\": [\n \"upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR\"\n ],\n \"attributes\": {\n \"gender\": \"female\",\n \"age\": 28,\n \"ethnicity\": \"east-asian\",\n \"hair_color\": \"black\",\n \"hair_style\": \"shoulder-length straight\",\n \"eye_color\": \"brown\",\n \"body_type\": \"athletic\",\n \"additional_details\": \"small scar above right eyebrow; warm smile\"\n },\n \"nudity\": \"clothed\",\n \"client_reference_id\": \"bot_8472\"\n}"
response = http.request(request)
puts response.read_body{
"id": "char_01HXMQ7Z3K8Y2NABCDEFGHJKMR",
"object": "character",
"name": "Aurora the Adventurer",
"status": "ready",
"nudity": "clothed",
"refs": [
{
"pose": "front",
"view": "full_front",
"source": "generated",
"url": "https://storage-host.example/storage/v1/object/sign/characters/full_front.jpg?token=eyJhbGciOi..."
}
],
"views": [
{
"view": "full_front",
"status": "generated",
"url": null,
"nudity": "clothed",
"reason": null,
"detected_view": null,
"made_from": [
"head_front",
"full_front"
]
}
],
"build": {
"missing_views": [
"full_front",
"full_right"
],
"cost_credits": 123,
"per_view_credits": 123
},
"created_at": "2026-05-08T10:00:00Z",
"updated_at": "2026-05-08T10:00:00Z",
"client_reference_id": "bot_8472",
"attributes": {
"gender": "female",
"age": 28,
"ethnicity": "east-asian",
"hair_color": "black",
"hair_style": "shoulder-length straight",
"eye_color": "brown",
"body_type": "athletic",
"additional_details": "small scar above right eyebrow; warm smile"
},
"error_message": null,
"aurous_version": "2026-08-26"
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}{
"error": {
"type": "invalid_request",
"code": "balance_too_low",
"message": "Team available balance is 1.5 credits, generation requires 2.0.",
"doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
"request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"param": "prompt",
"reason": "wrong_view",
"detected_view": "full_left"
}
}POST /v1/characters creates a character — a reusable identity asset you can attach to an image or video generation via character_id (see Create an image or Create a video). There are two mutually exclusive flows:
- Upload flow: pass
upload_ids(1–6) collected fromPOST /v1/characters/uploads/init. Those photos are the INPUT; the platform renders the character’s eight named views from them. The response returns once the request is validated and priced (typically a few seconds — allow at least 30 s of client timeout) withstatus: synthesizingandrefs: []— not a usable character yet. PollGET /v1/characters/{id}(or subscribe to thecharacter.completedwebhook) untilstatus: ready; the character advances there on its own, with no save step. - Synthesize flow: pass
generate: trueplus anattributesobject describing who the character is. The platform renders the eight named views (head_front,upper_front,lower_front,upper_back,lower_back,full_left,full_front,full_right). The response returns once the request is validated and priced (typically a few seconds — allow at least 30 s of client timeout) withstatus: synthesizingandrefs: []. PollGET /v1/characters/{id}(or subscribe to thecharacter.completedwebhook) untilstatus: ready; the character advances there on its own, with no save step.
upload_ids or generate: true — never both, never neither.
Sending both or neither returns 400 parameter_invalid_combination.POST /v1/characters/drafts creates a free character in draft, you file your own photo into each view with PUT /v1/characters/{id}/refs/{view}, and POST /v1/characters/{id}/build renders only what is still missing — so you pay per rendered view instead of for all eight. This route stays the one-shot path: it charges for eight views up front and renders them immediately. See Views and lifecycle.2026-08-26 contract. No Aurous-Version pin isolates them: the version catalogue carries image and video pricing pointers only, not character pricing or the size of the reference set. Pinning an earlier Aurous-Version restores neither the smaller reference set nor the previous price. A character created before 2026-09-14 keeps the references it already has — add either of the two newer views on demand with POST /v1/characters/{id}/refs/{view}/regenerate; every character created on or after that date carries eight.Upload flow
Use this when you already have ref images. Mint oneupload_id per file via POST /v1/characters/uploads/init, PUT the bytes, then create the character.
curl -X POST https://api.aurous-labs.com/v1/characters \
-H "X-Api-Key: $AUROUS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "Aurora",
"upload_ids": [
"upl_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"upl_01HXMQ87RKZQA0YBYV1V47TPS6"
]
}'
const character = await fetch("https://api.aurous-labs.com/v1/characters", {
method: "POST",
headers: {
"X-Api-Key": process.env.AUROUS_API_KEY!,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
name: "Aurora",
upload_ids: ["upl_01HXMQ7Z3K8Y2VNABCDEFGHJKM", "upl_01HXMQ87RKZQA0YBYV1V47TPS6"],
}),
}).then((r) => r.json());
console.log(character.id, character.status); // char_..., "synthesizing"
import os, uuid, requests
r = requests.post(
"https://api.aurous-labs.com/v1/characters",
headers={
"X-Api-Key": os.environ["AUROUS_API_KEY"],
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"name": "Aurora",
"upload_ids": [
"upl_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
"upl_01HXMQ87RKZQA0YBYV1V47TPS6",
],
},
).json()
print(r["id"], r["status"]) # char_..., "synthesizing"
Synthesize flow
Use this when you want the platform to generate the refs from a description. Theattributes object is locked at v1.0 to 7 typed fields plus a free-text additional_details catch-all; on this synthesize flow every field drives generation (on the upload flow only additional_details is used, and only as a best-effort hint — see Upload flow). Synthesize burns credits at create time — eight renders, priced at build.per_view_credits each — so read build.per_view_credits off any character rather than hard-coding a price.
curl -X POST https://api.aurous-labs.com/v1/characters \
-H "X-Api-Key: $AUROUS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "Aurora",
"generate": true,
"attributes": {
"gender": "female",
"age": 28,
"ethnicity": "northern european",
"hair_color": "auburn",
"hair_style": "long waves",
"eye_color": "green",
"body_type": "athletic",
"additional_details": "scar across left cheekbone, freckles"
}
}'
const character = await fetch("https://api.aurous-labs.com/v1/characters", {
method: "POST",
headers: {
"X-Api-Key": process.env.AUROUS_API_KEY!,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
name: "Aurora",
generate: true,
attributes: {
gender: "female",
age: 28,
ethnicity: "northern european",
hair_color: "auburn",
hair_style: "long waves",
eye_color: "green",
body_type: "athletic",
additional_details: "scar across left cheekbone, freckles",
},
}),
}).then((r) => r.json());
console.log(character.id, character.status); // char_..., "synthesizing"
import os, uuid, requests
r = requests.post(
"https://api.aurous-labs.com/v1/characters",
headers={
"X-Api-Key": os.environ["AUROUS_API_KEY"],
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"name": "Aurora",
"generate": True,
"attributes": {
"gender": "female",
"age": 28,
"ethnicity": "northern european",
"hair_color": "auburn",
"hair_style": "long waves",
"eye_color": "green",
"body_type": "athletic",
"additional_details": "scar across left cheekbone, freckles",
},
},
).json()
print(r["id"], r["status"]) # char_..., "synthesizing"
Nudity
nudity on create decides what the whole reference set is rendered as — nude or clothed. Omit it and the character takes the team default: nude unless your team’s default is clothed. On teams that always render clothed, an explicit nudity: "nude" returns 400 nudity_not_allowed.
The choice is fixed once the references are built. It is echoed back as nudity on every character read, and a per-view regenerate re-renders at the character’s own nudity — to change it, create a new character.
Status transitions
| Flow | Initial | After processing | Customer action |
|---|---|---|---|
| Upload | synthesizing | ready | Poll GET /v1/characters/{id} (or take the character.completed webhook), then use it on POST /v1/images or POST /v1/videos. |
| Synthesize | synthesizing | ready | Poll GET /v1/characters/{id} (or take the character.completed webhook), then use it on POST /v1/images or POST /v1/videos. |
| Either (failed) | synthesizing | failed | error_message populated and the up-front charge released. Call POST /v1/characters/{id}/resynthesize to retry (reuses the stored attributes); if it returns 422 uploads_expired, delete via DELETE /v1/characters/{id} and recreate with fresh upload_ids. |
draft — that status belongs to the builder. And a one-shot character is not builder lineage: its source photos are still upload tickets, so POST /v1/characters/{id}/build refuses it with 400 character_status_invalid and points at resynthesize instead.
Neither flow stops at reviewing, so POST /v1/characters/{id}/save is not a step in either one. It exists for a character left in reviewing by Resynthesize or by a build without auto_save: true.
Limits
- Rate limit: bucket
characters_synthesize— 15 requests/min sustained, 30 burst per team. Both flows ride this bucket since the synthesize discriminator is decided server-side after the request lands. Shared acrossPOST /v1/characters,POST /v1/characters/{id}/refs/regenerate,POST /v1/characters/{id}/refs/{view}/regenerate,POST /v1/characters/{id}/resynthesizeandPOST /v1/characters/{id}/build— five routes, one bucket. - Idempotency: pass
Idempotency-Key(any opaque value, 1–256 chars). Same key + same body within 24h replays the cached response withAurous-Idempotent-Replayed: true. Same key + different body returns409 idempotency_key_in_use. See Idempotency for details.
Idempotency-Key on create — either flow dispatches eight
paid renders, so a network retry without a key can double-charge.Response fields added in this release
Five fields are additive — nothing was removed or renamed, and every existing field keeps its meaning. They appear on every character response (create, retrieve, list, and the action endpoints).| Field | What it is |
|---|---|
nudity | nude or clothed — what the reference set was rendered as. Fixed once the references are built. |
views[] | The canonical read model: one entry per named view in canonical order, including the ones this character does not have (status: "missing"). Each entry carries view, status, url, nudity, reason, detected_view and made_from. Prefer this over refs[] for new integrations. |
build | What a render would produce and cost right now: missing_views[], cost_credits, per_view_credits. Read per_view_credits instead of hardcoding a price. |
refs[].view | The real view name for a reference. refs[].pose keeps the original five-value label for compatibility and projects several views onto other; view does not. |
refs[].source | uploaded (a photo you supplied, used as-is) or generated (rendered by the platform). |
Errors
| Code | HTTP | When |
|---|---|---|
parameter_invalid_combination | 400 | Sent both upload_ids and generate: true, or neither. |
nudity_not_allowed | 400 | nudity: "nude" on a team that always renders clothed. |
balance_too_low | 402 | Either flow, when team credits < cost. Both are charged 8 × build.per_view_credits up front. |
idempotency_key_in_use | 409 | Same Idempotency-Key was used with a different body. |
too_many_requests | 429 | Burst > 30 or sustained > 15/min. |
Common pitfalls
- Both flows return 201 with
status: "synthesizing", but the character is not usable until it reachesready, which both flows do on their own — there is no save step on create. CallingPOST /v1/imagesorPOST /v1/videoswith asynthesizingcharacter_idreturns400 character_not_ready. - Upload tickets are not consumed by a create. A ticket stays usable until its
expires_at; every create that lists it is a separate character and a separate charge. If you retry a create, either reuse the ticket deliberately or guard the retry with anIdempotency-Key. - An upload flow with a single ref still works. On the upload flow your reference images define identity, so the 7 typed
attributes(gender, age, etc.) are stored and echoed back but do not shape the generated refs — onlyadditional_detailsis applied there, and only as a best-effort hint (your reference images are the primary signal). The synthesize flow (generate: true) is the opposite: allattributesdrive generation, so don’t send emptyattributesthere. - The
attributesschema is locked for v1.0; new attributes go intoadditional_detailsuntil a v1.1 bump introduces them as typed fields.
Authorizations
Your team API key (starts with al_live_).
Headers
Stripe-style idempotency key (1-256 chars). Same key + same canonical-JSON body returns the cached response with Aurous-Idempotent-Replayed: true. Same key against a different route (e.g. previously used on /v1/images) returns 409 invalid_request / idempotency_key_in_use. Replay window is 24 hours. Absent header is treated as non-idempotent (each call processes anew).
Optional API version pin (YYYY-MM-DD). Omit the header to receive the platform default, currently 2026-08-26.
^\d{4}-\d{2}-\d{2}$"2026-08-26"
Body
Display name for the character (1-80 chars).
1 - 80"Aurora the Adventurer"
Selects the synthesize flow when true (the platform renders the eight named views head_front, upper_front, lower_front, upper_back, lower_back, full_left, full_front, full_right from attributes). When omitted/false, upload_ids must be provided to attach customer-supplied refs. Mutually exclusive with upload_ids.
false
Upload tickets from POST /v1/characters/uploads/init (1-6). When set, the upload flow is used and the server moves bytes from upload storage to character storage on create (uploads are consumed). Mutually exclusive with generate: true.
1 - 6 elements["upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR"]
Character attributes. Required when generate: true (synthesize flow), where ALL fields drive generation. Optional on the upload flow, where the 7 typed fields (gender, age, ethnicity, hair_color, hair_style, eye_color, body_type) are stored and echoed back but do NOT shape the generated refs — your uploaded images define identity. additional_details is the exception: it is also applied on the upload flow as a free-text styling hint, but treat it as best-effort — your uploaded reference images are the primary signal, so supply a reference image for any trait you need to guarantee.
Show child attributes
Show child attributes
What the reference set is rendered as. An explicit value always wins; omit it for your team's default, which is nude unless your team's default is clothed. On teams that always render clothed an explicit nude returns 400 nudity_not_allowed. Fixed once the references are built.
nude, clothed "clothed"
Optional caller-supplied reference, echoed back on GET /v1/characters/{id} and in every character webhook for this character. Use it to correlate the webhook with the originating record in your system (Stripe-style). Immutable after create. Max 256 chars.
256"bot_8472"
Response
Character accepted; synthesis dispatched (status: synthesizing, refs: []).
Opaque character ID.
"char_01HXMQ7Z3K8Y2NABCDEFGHJKMR"
Discriminator
character "character"
Display name.
"Aurora the Adventurer"
Lifecycle state. draft: references are being assembled; nothing has been charged and the character cannot be used for generation. New lifecycle states may be added in future — treat any status other than ready as "not yet usable". synthesizing: references are being built. ready: usable on POST /v1/images — both create flows advance here on their own. reviewing: reached only after POST /:id/resynthesize or POST /:id/build; call POST /:id/save to return to ready. failed: the build failed; error_message carries the reason and POST /:id/build (builder characters) or POST /:id/resynthesize retries. deleted: soft-deleted (filtered out of the list endpoint).
draft, synthesizing, reviewing, ready, failed, deleted "ready"
What the reference set is rendered as — nude unless you chose clothed, or — when you omitted nudity — your team's default is clothed. An explicit value always wins. Fixed once the references are built.
nude, clothed "clothed"
Reference images, one per view the character has. A character built today has eight; an older one can have fewer than eight (typically six, some of the oldest four). Do not assume a count — read views[], the canonical read model for new integrations, or build.missing_views. refs[] lists only the references that exist.
Show child attributes
Show child attributes
One entry per named view, in canonical order — every reference that maps to a named view, keyed by view, plus what is missing or was rejected. A reference from before named views existed (refs[].view is other) appears only in refs[].
Show child attributes
Show child attributes
What a build would render and cost right now. Always present.
Show child attributes
Show child attributes
Creation timestamp (ISO 8601).
"2026-05-08T10:00:00Z"
Last-update timestamp (ISO 8601).
"2026-05-08T10:00:00Z"
Caller-supplied reference echoed back (the value sent on create). Null if unset.
"bot_8472"
Character attributes. Null when unset.
Show child attributes
Show child attributes
Machine-readable failure code from the most recent synthesize/resynthesize attempt. One of synthesis_failed, synthesis_timeout, provider_unavailable — a closed set to switch on, not customer-facing prose. Null when the most recent attempt succeeded (or none has run yet). Set on failed, but NOT failed-exclusive: a failed resynthesize restores the character to its prior working status (reviewing) rather than overwriting a good generation, so a reviewing row can carry a non-null code here — check this field for failure, not status. Cleared on the next resynthesize attempt and on success.
null
API contract version applied at the time this row was minted (D25 — frozen for replay across future version bumps).
"2026-08-26"

