{
	"openapi": "3.0.3",
	"info": {
		"title": "OhAPI Documentation",
		"description": "Interactive API documentation for OH's Media Generation APIs",
		"version": "1.0.0"
	},
	"servers": [
		{
			"url": "https://api.oh.xyz",
			"description": "Production API Server"
		}
	],
	"security": [
		{
			"bearerAuth": []
		}
	],
	"paths": {
		"/api/v1/rooms": {
			"post": {
				"tags": ["Rooms"],
				"summary": "Create Room",
				"description": "Create a new conversation room for a customer-character pair. The returned room_id should be used for all subsequent text, image, audio, and video requests.",
				"operationId": "createRoom",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/rooms', {\n  method: 'POST',\n  headers: {\n    'Content-Type': 'application/json',\n    'x-api-key': 'YOUR_API_KEY',\n    \n  },\n  body: JSON.stringify({\n    user_gender: 'male',\n    character_id: '154'\n  })\n});\n\nif (!response.ok) {\n  throw new Error(`Request failed: ${response.status}`);\n}\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X POST 'https://api.oh.xyz/api/v1/rooms' \\\n  -H 'Content-Type: application/json' \\\n  -H 'x-api-key: YOUR_API_KEY' \\\n  -d '{\n    \"user_gender\": \"male\",\n    \"character_id\": \"154\"\n  }'"
					},
					{
						"lang": "Python",
						"source": "import requests\nimport json\n\nurl = 'https://api.oh.xyz/api/v1/rooms'\nheaders = {\n    'Content-Type': 'application/json',\n    'x-api-key': 'YOUR_API_KEY',\n    \n}\ndata = {\n    'user_gender': 'male',\n    'character_id': '154'\n}\n\nresponse = requests.post(url, headers=headers, json=data)\nresponse.raise_for_status()\nprint(response.json())"
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"required": ["user_gender", "character_id"],
								"properties": {
									"user_gender": {
										"type": "string",
										"description": "Gender of the user (e.g., male, female)"
									},
									"character_id": {
										"type": "string",
										"description": "Unique identifier for the character/supermodel"
									},
									"texting_style": {
										"type": "string",
										"enum": ["default", "short-form", "long-form"],
										"description": "Optional reply register for this room. 'default' (used when omitted) keeps the existing production style; 'short-form' is a brief, punchy chat-speak register; 'long-form' is a warm, natural register. Can be changed later via PATCH /api/v1/rooms/{room_id}/texting-style."
									}
								}
							},
							"example": {
								"user_gender": "male",
								"character_id": "154"
							}
						}
					}
				},
				"responses": {
					"201": {
						"description": "Room created successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"room_id": {
											"type": "string",
											"format": "uuid",
											"description": "Unique identifier for the created room"
										}
									}
								},
								"example": {
									"room_id": "your-room-id-here"
								}
							}
						}
					}
				}
			}
		},
		"/api/v1/rooms/{room_id}/texting-style": {
			"put": {
				"tags": ["Rooms"],
				"summary": "Set Room Texting Style",
				"description": "Set the reply register for an existing room. 'default' keeps the production style every room starts with; 'short-form' is a brief, punchy chat-speak register; 'long-form' is a warm, natural register. Takes effect from the next generated reply (text and audio). Rooms created before this feature behave as 'default'.",
				"operationId": "replaceRoomTextingStyle",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/rooms/YOUR_ROOM_ID/texting-style', {\n  method: 'PUT',\n  headers: {\n    'Content-Type': 'application/json',\n    'x-api-key': 'YOUR_API_KEY',\n    \n  },\n  body: JSON.stringify({\n    texting_style: 'long-form'\n  })\n});\n\nif (!response.ok) {\n  throw new Error(`Request failed: ${response.status}`);\n}\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X PUT 'https://api.oh.xyz/api/v1/rooms/YOUR_ROOM_ID/texting-style' \\\n  -H 'Content-Type: application/json' \\\n  -H 'x-api-key: YOUR_API_KEY' \\\n  -d '{\n    \"texting_style\": \"long-form\"\n  }'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/rooms/YOUR_ROOM_ID/texting-style'\nheaders = {\n    'Content-Type': 'application/json',\n    'x-api-key': 'YOUR_API_KEY',\n    \n}\ndata = {\n    'texting_style': 'long-form'\n}\n\nresponse = requests.put(url, headers=headers, json=data)\nresponse.raise_for_status()\nprint(response.json())"
					}
				],
				"parameters": [
					{
						"name": "room_id",
						"in": "path",
						"required": true,
						"schema": { "type": "string", "format": "uuid" },
						"description": "The room to update"
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"required": ["texting_style"],
								"properties": {
									"texting_style": {
										"type": "string",
										"enum": ["default", "short-form", "long-form"],
										"description": "Reply register for the room. Applies from the next generated reply (text and audio)."
									}
								}
							},
							"example": {
								"texting_style": "long-form"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Room updated; returns the room view including the new texting_style",
						"content": {
							"application/json": {
								"example": {
									"room_id": "your-room-id-here",
									"texting_style": "long-form"
								}
							}
						}
					},
					"400": {
						"description": "Invalid texting_style value"
					},
					"404": {
						"description": "Room not found"
					}
				}
			},
			"patch": {
				"tags": ["Rooms"],
				"summary": "Set Room Texting Style",
				"description": "Set the reply register for an existing room. 'default' keeps the production style every room starts with; 'short-form' is a brief, punchy chat-speak register; 'long-form' is a warm, natural register. Takes effect from the next generated reply (text and audio). Rooms created before this feature behave as 'default'.",
				"operationId": "updateRoomTextingStyle",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/rooms/YOUR_ROOM_ID/texting-style', {\n  method: 'PATCH',\n  headers: {\n    'Content-Type': 'application/json',\n    'x-api-key': 'YOUR_API_KEY',\n    \n  },\n  body: JSON.stringify({\n    texting_style: 'long-form'\n  })\n});\n\nif (!response.ok) {\n  throw new Error(`Request failed: ${response.status}`);\n}\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X PATCH 'https://api.oh.xyz/api/v1/rooms/YOUR_ROOM_ID/texting-style' \\\n  -H 'Content-Type: application/json' \\\n  -H 'x-api-key: YOUR_API_KEY' \\\n  -d '{\n    \"texting_style\": \"long-form\"\n  }'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/rooms/YOUR_ROOM_ID/texting-style'\nheaders = {\n    'Content-Type': 'application/json',\n    'x-api-key': 'YOUR_API_KEY',\n    \n}\ndata = {\n    'texting_style': 'long-form'\n}\n\nresponse = requests.patch(url, headers=headers, json=data)\nresponse.raise_for_status()\nprint(response.json())"
					}
				],
				"parameters": [
					{
						"name": "room_id",
						"in": "path",
						"required": true,
						"schema": { "type": "string", "format": "uuid" },
						"description": "The room to update"
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"required": ["texting_style"],
								"properties": {
									"texting_style": {
										"type": "string",
										"enum": ["default", "short-form", "long-form"],
										"description": "Reply register for the room. Applies from the next generated reply (text and audio)."
									}
								}
							},
							"example": {
								"texting_style": "long-form"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Room updated; returns the room view including the new texting_style",
						"content": {
							"application/json": {
								"example": {
									"room_id": "your-room-id-here",
									"texting_style": "long-form"
								}
							}
						}
					},
					"400": {
						"description": "Invalid texting_style value"
					},
					"404": {
						"description": "Room not found"
					}
				}
			}
		},
		"/api/v1/text": {
			"post": {
				"tags": ["Text"],
				"summary": "Generate Text",
				"description": "Generate a text response from the character in a specific room.",
				"operationId": "generateText",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"required": ["room_id", "prompt"],
								"properties": {
									"room_id": {
										"type": "string",
										"description": "Room ID from the create room endpoint"
									},
									"prompt": {
										"type": "string",
										"description": "User's message or prompt"
									}
								}
							},
							"example": {
								"room_id": "bcbb245f-ff8c-4f67-9fd6-7d0bd814d8e3",
								"prompt": "Tell me about your favorite travel destination"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Text generated successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"content": {
											"type": "string",
											"description": "Generated text response from the character"
										}
									}
								},
								"example": {
									"content": "Your generated text response will appear here"
								}
							}
						}
					}
				}
			}
		},
		"/api/v1/images": {
			"post": {
				"tags": ["Images"],
				"summary": "Generate Image (Async)",
				"description": "Generate an image of the character. This is an asynchronous operation — submit the request, receive a job_id and presigned_url, then poll GET /api/v1/jobs/{job_id}/status until the job completes. The presigned_url will contain the generated image once the job status is \"completed\".",
				"operationId": "generateImage",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/images', {\n  method: 'POST',\n  headers: {\n    'Content-Type': 'application/json',\n    'x-api-key': 'YOUR_API_KEY'\n  },\n  body: JSON.stringify({\n    character_id: '154',\n    prompt: 'A stunning selfie at sunset on the beach',\n    prompt_enhancement: false,\n    resolution: '9:16'\n  })\n});\n\nconst data = await response.json();\nconst { job_id, presigned_url } = data;\nconsole.log('Job ID:', job_id);\nconsole.log('Download URL:', presigned_url);"
					},
					{
						"lang": "cURL",
						"source": "curl -X POST 'https://api.oh.xyz/api/v1/images' \\\n  -H 'Content-Type: application/json' \\\n  -H 'x-api-key: YOUR_API_KEY' \\\n  -d '{\n    \"character_id\": \"154\",\n    \"prompt\": \"A stunning selfie at sunset on the beach\",\n    \"prompt_enhancement\": false,\n    \"resolution\": \"9:16\"\n  }'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nresponse = requests.post(\n    'https://api.oh.xyz/api/v1/images',\n    headers={'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY'},\n    json={\n        'character_id': '154',\n        'prompt': 'A stunning selfie at sunset on the beach',\n        'prompt_enhancement': False,\n        'resolution': '9:16'\n    }\n)\nresult = response.json()\njob_id = result['job_id']\npresigned_url = result['presigned_url']\nprint('Job ID:', job_id)\nprint('Download URL:', presigned_url)"
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"required": ["character_id", "prompt"],
								"properties": {
									"character_id": {
										"type": "string",
										"description": "Character identifier for generation"
									},
									"prompt": {
										"type": "string",
										"description": "Description of the image to generate"
									},
									"prompt_enhancement": {
										"type": "boolean",
										"default": false,
										"description": "When enabled, the system enhances your prompt using AI to improve image quality and detail. When disabled, your prompt is used as-is."
									},
									"user_gender": {
										"type": "string",
										"enum": ["male", "female"],
										"description": "Gender of the user. Used to tailor the generated scene when prompt enhancement is enabled. If omitted, defaults to the opposite of the character's gender."
									},
									"resolution": {
										"oneOf": [
											{
												"type": "array",
												"items": {
													"type": "integer"
												},
												"minItems": 2,
												"maxItems": 2,
												"description": "Explicit [width, height] in pixels"
											},
											{
												"type": "string",
												"enum": ["9:16", "16:9", "1:1", "4:3", "3:4"],
												"description": "Aspect ratio preset"
											}
										],
										"description": "Output resolution. Can be an aspect ratio string (\"9:16\", \"16:9\", \"1:1\", \"4:3\", \"3:4\") or an explicit [width, height] array. Aspect ratio presets map to: 9:16 → 720×1280, 16:9 → 1280×720, 1:1 → 1024×1024, 4:3 → 960×720, 3:4 → 720×960."
									}
								}
							},
							"example": {
								"character_id": "154",
								"prompt": "A stunning selfie at sunset on the beach",
								"prompt_enhancement": false,
								"resolution": "9:16"
							}
						}
					}
				},
				"responses": {
					"202": {
						"description": "Image generation job accepted",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"message": {
											"type": "string",
											"description": "Status message"
										},
										"job_id": {
											"type": "string",
											"description": "Unique identifier for tracking job status"
										},
										"status": {
											"type": "string",
											"description": "Current status (processing, completed, failed)"
										},
										"presigned_url": {
											"type": "string",
											"description": "S3 presigned URL where the image will be available"
										}
									}
								},
								"example": {
									"message": "Image generation started",
									"job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
									"status": "processing",
									"presigned_url": "https://s3.amazonaws.com/bucket/generated/image/154/2026-04/a1b2c3d4.png?X-Amz-..."
								}
							}
						}
					}
				}
			}
		},
		"/api/v1/jobs/{job_id}/status": {
			"get": {
				"tags": ["Jobs"],
				"summary": "Check Job Status",
				"description": "Poll this endpoint to check the status of an asynchronous job (image generation or text-to-video). Use the job_id received from the generation response. For image-to-video jobs, use GET /api/v1/videos/get instead. Recommended polling interval: every 2–5 seconds, with a maximum timeout of 5 minutes.",
				"operationId": "getJobStatus",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "async function pollJob(jobId, apiKey, maxAttempts = 60) {\n  for (let i = 0; i < maxAttempts; i++) {\n    const response = await fetch(\n      `https://api.oh.xyz/api/v1/jobs/${jobId}/status`,\n      { headers: { 'x-api-key': apiKey } }\n    );\n    const data = await response.json();\n\n    if (data.status === 'completed') {\n      console.log('Download URL:', data.url);\n      return data;\n    }\n    if (data.status === 'failed') {\n      throw new Error(data.error || 'Job failed');\n    }\n\n    await new Promise(r => setTimeout(r, 3000));\n  }\n  throw new Error('Polling timed out');\n}"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/jobs/{job_id}/status' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\nimport time\n\ndef poll_job(job_id, api_key, max_attempts=60):\n    url = f'https://api.oh.xyz/api/v1/jobs/{job_id}/status'\n    headers = {'x-api-key': api_key}\n\n    for _ in range(max_attempts):\n        data = requests.get(url, headers=headers).json()\n\n        if data['status'] == 'completed':\n            print('Download URL:', data['url'])\n            return data\n        if data['status'] == 'failed':\n            raise Exception(data.get('error', 'Job failed'))\n\n        time.sleep(3)\n\n    raise TimeoutError('Polling timed out')"
					}
				],
				"parameters": [
					{
						"name": "job_id",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string"
						},
						"description": "The job ID returned from image generation request"
					}
				],
				"responses": {
					"200": {
						"description": "Job status retrieved successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"job_id": {
											"type": "string",
											"description": "The job identifier"
										},
										"status": {
											"type": "string",
											"enum": ["queued", "processing", "completed", "failed"],
											"description": "Current job status"
										},
										"url": {
											"type": "string",
											"nullable": true,
											"description": "Presigned download URL for the generated content. Only present when status is \"completed\"."
										},
										"results": {
											"type": "object",
											"nullable": true,
											"description": "Additional result data. May contain \"image_prompt\" (the enhanced prompt that was used) when prompt_enhancement was enabled.",
											"properties": {
												"image_prompt": {
													"type": "string",
													"description": "The enhanced prompt that was actually used for generation (only present if prompt_enhancement was enabled)"
												}
											}
										},
										"error": {
											"type": "string",
											"nullable": true,
											"description": "Error message. Only present when status is \"failed\"."
										}
									}
								},
								"examples": {
									"queued": {
										"summary": "Job is queued",
										"value": {
											"job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
											"status": "queued",
											"url": null,
											"results": null,
											"error": null
										}
									},
									"processing": {
										"summary": "Job is being processed",
										"value": {
											"job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
											"status": "processing",
											"url": null,
											"results": null,
											"error": null
										}
									},
									"completed": {
										"summary": "Job completed successfully",
										"value": {
											"job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
											"status": "completed",
											"url": "https://s3.amazonaws.com/bucket/generated/image/154/2026-04/a1b2c3d4.png?X-Amz-...",
											"results": {
												"image_prompt": "A photograph of the character at sunset..."
											},
											"error": null
										}
									},
									"failed": {
										"summary": "Job failed",
										"value": {
											"job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
											"status": "failed",
											"url": null,
											"results": null,
											"error": "Generation failed: timeout"
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/api/v1/audio/notes": {
			"post": {
				"tags": ["Audio"],
				"summary": "Generate Audio Note",
				"description": "Generate an audio note from the character in a specific room.",
				"operationId": "generateAudio",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"required": ["room_id", "prompt"],
								"properties": {
									"room_id": {
										"type": "string",
										"description": "Room ID from the create room endpoint"
									},
									"prompt": {
										"type": "string",
										"description": "Text for the character to speak"
									}
								}
							},
							"example": {
								"room_id": "bcbb245f-ff8c-4f67-9fd6-7d0bd814d8e3",
								"prompt": "Tell me about your favorite travel destination"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Audio generated successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"url": {
											"type": "string",
											"description": "Direct URL to the generated audio file"
										}
									}
								},
								"example": {
									"url": "your-audio-url-here"
								}
							}
						}
					}
				}
			}
		},
		"/api/v1/terms/eye_color": {
			"get": {
				"tags": ["Taxonomy"],
				"summary": "Get Eye Color Terms",
				"description": "Retrieve available eye color options with their term IDs.",
				"operationId": "getEyeColorTerms",
				"responses": {
					"200": {
						"description": "Terms retrieved successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "object",
										"properties": {
											"name": {
												"type": "string"
											},
											"tid": {
												"type": "string"
											}
										}
									}
								},
								"example": [
									{
										"name": "Green",
										"tid": "233"
									},
									{
										"name": "Brown",
										"tid": "241"
									}
								]
							}
						}
					}
				}
			}
		},
		"/api/v1/cam/create": {
			"post": {
				"tags": ["Interactive Cam Avatars"],
				"summary": "Create Cam Avatar Session",
				"description": "Initialize avatar session with character UID. Returns webhook configuration and session details for handling real-time conversations.",
				"operationId": "createCamSession",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"required": ["character_uid", "webhook_url"],
								"properties": {
									"character_uid": {
										"type": "string",
										"description": "Unique identifier for the character"
									},
									"webhook_url": {
										"type": "string",
										"format": "uri",
										"description": "Your REST endpoint URL to receive webhook events"
									}
								}
							},
							"example": {
								"character_uid": "550e8400-e29b-41d4-a716-446655440000",
								"webhook_url": "https://your-domain.com/webhook"
							}
						}
					}
				},
				"responses": {
					"201": {
						"description": "Cam session created successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"session_id": {
											"type": "string",
											"description": "Unique session identifier"
										},
										"callback_url": {
											"type": "string",
											"format": "uri",
											"description": "URL for sending proactive messages"
										},
										"status": {
											"type": "string",
											"description": "Session status"
										}
									}
								},
								"example": {
									"session_id": "cam_abc123xyz",
									"callback_url": "https://trulience.com/tglisten",
									"status": "active"
								}
							}
						}
					}
				}
			}
		},
		"/api/v1/cam/sessions": {
			"get": {
				"tags": ["Interactive Cam Avatars"],
				"summary": "List Active Cam Sessions",
				"description": "Retrieve all active avatar sessions with pagination support.",
				"operationId": "listCamSessions",
				"parameters": [
					{
						"name": "page",
						"in": "query",
						"required": false,
						"schema": {
							"type": "integer",
							"default": 1
						},
						"description": "Page number for pagination"
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"type": "integer",
							"default": 10
						},
						"description": "Number of results per page"
					}
				],
				"responses": {
					"200": {
						"description": "List of active sessions",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"sessions": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"session_id": {
														"type": "string"
													},
													"character_uid": {
														"type": "string"
													},
													"status": {
														"type": "string"
													},
													"created_at": {
														"type": "string",
														"format": "date-time"
													}
												}
											}
										},
										"total": {
											"type": "integer"
										},
										"page": {
											"type": "integer"
										},
										"limit": {
											"type": "integer"
										}
									}
								},
								"example": {
									"sessions": [
										{
											"session_id": "cam_abc123xyz",
											"character_uid": "550e8400-e29b-41d4-a716-446655440000",
											"status": "active",
											"created_at": "2024-01-15T10:30:00Z"
										}
									],
									"total": 1,
									"page": 1,
									"limit": 10
								}
							}
						}
					}
				}
			}
		},
		"/api/v1/cam/sessions/{sessionId}": {
			"delete": {
				"tags": ["Interactive Cam Avatars"],
				"summary": "End Cam Session",
				"description": "Manually terminate avatar session. Triggers cleanup and sends logout event to webhook.",
				"operationId": "endCamSession",
				"parameters": [
					{
						"name": "sessionId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string"
						},
						"description": "The session ID to terminate"
					}
				],
				"responses": {
					"200": {
						"description": "Session ended successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"message": {
											"type": "string"
										},
										"session_id": {
											"type": "string"
										}
									}
								},
								"example": {
									"message": "Session ended successfully",
									"session_id": "cam_abc123xyz"
								}
							}
						}
					}
				}
			}
		},
		"/api/v1/terms/body_type": {
			"get": {
				"tags": ["Taxonomy"],
				"summary": "Get Body Type Terms",
				"description": "Retrieve available body type options with their term IDs.",
				"operationId": "getBodyTypeTerms",
				"responses": {
					"200": {
						"description": "Terms retrieved successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "object",
										"properties": {
											"name": {
												"type": "string"
											},
											"tid": {
												"type": "string"
											}
										}
									}
								},
								"example": [
									{
										"name": "Athletic",
										"tid": "45"
									},
									{
										"name": "Slim",
										"tid": "46"
									}
								]
							}
						}
					}
				}
			}
		},
		"/api/v1/terms/gender": {
			"get": {
				"tags": ["Taxonomy"],
				"summary": "Get Gender Terms",
				"description": "Retrieve available gender options with their term IDs.",
				"operationId": "getGenderTerms",
				"responses": {
					"200": {
						"description": "Terms retrieved successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "object",
										"properties": {
											"name": {
												"type": "string"
											},
											"tid": {
												"type": "string"
											}
										}
									}
								},
								"example": [
									{
										"name": "Female",
										"tid": "2"
									},
									{
										"name": "Male",
										"tid": "3"
									}
								]
							}
						}
					}
				}
			}
		},
		"/api/v1/terms/hair_color": {
			"get": {
				"tags": ["Taxonomy"],
				"summary": "Get Hair Color Terms",
				"description": "Retrieve available hair color options with their term IDs.",
				"operationId": "getHairColorTerms",
				"responses": {
					"200": {
						"description": "Terms retrieved successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "object",
										"properties": {
											"name": {
												"type": "string"
											},
											"tid": {
												"type": "string"
											}
										}
									}
								},
								"example": [
									{
										"name": "Blonde",
										"tid": "12"
									},
									{
										"name": "Brunette",
										"tid": "13"
									}
								]
							}
						}
					}
				}
			}
		},
		"/api/v1/terms/orientation": {
			"get": {
				"tags": ["Taxonomy"],
				"summary": "Get Orientation Terms",
				"description": "Retrieve available sexual orientation options with their term IDs.",
				"operationId": "getOrientationTerms",
				"responses": {
					"200": {
						"description": "Terms retrieved successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "object",
										"properties": {
											"name": {
												"type": "string"
											},
											"tid": {
												"type": "string"
											}
										}
									}
								},
								"example": [
									{
										"name": "Straight",
										"tid": "4"
									},
									{
										"name": "Bisexual",
										"tid": "5"
									}
								]
							}
						}
					}
				}
			}
		},
		"/api/v1/terms/ethnicity": {
			"get": {
				"tags": ["Taxonomy"],
				"summary": "Get Ethnicity Terms",
				"description": "Retrieve available ethnicity options with their term IDs.",
				"operationId": "getEthnicityTerms",
				"responses": {
					"200": {
						"description": "Terms retrieved successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "object",
										"properties": {
											"name": {
												"type": "string"
											},
											"tid": {
												"type": "string"
											}
										}
									}
								},
								"example": [
									{
										"name": "Asian",
										"tid": "15"
									},
									{
										"name": "Caucasian",
										"tid": "16"
									}
								]
							}
						}
					}
				}
			}
		},
		"/api/v1/terms/breast_size": {
			"get": {
				"tags": ["Taxonomy"],
				"summary": "Get Breast Size Terms",
				"description": "Retrieve available breast size options with their term IDs.",
				"operationId": "getBreastSizeTerms",
				"responses": {
					"200": {
						"description": "Terms retrieved successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "object",
										"properties": {
											"name": {
												"type": "string"
											},
											"tid": {
												"type": "string"
											}
										}
									}
								},
								"example": [
									{
										"name": "Small",
										"tid": "201"
									},
									{
										"name": "Medium",
										"tid": "202"
									}
								]
							}
						}
					}
				}
			}
		},
		"/api/v1/terms/interests": {
			"get": {
				"tags": ["Taxonomy"],
				"summary": "Get Interests Terms",
				"description": "Retrieve available interests and passions with their term IDs.",
				"operationId": "getInterestsTerms",
				"responses": {
					"200": {
						"description": "Terms retrieved successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "object",
										"properties": {
											"name": {
												"type": "string"
											},
											"tid": {
												"type": "string"
											}
										}
									}
								},
								"example": [
									{
										"name": "Art",
										"tid": "90"
									},
									{
										"name": "Music",
										"tid": "91"
									}
								]
							}
						}
					}
				}
			}
		},
		"/api/v1/terms/kinks": {
			"get": {
				"tags": ["Taxonomy"],
				"summary": "Get Kinks Terms",
				"description": "Retrieve available kinks and preferences with their term IDs.",
				"operationId": "getKinksTerms",
				"responses": {
					"200": {
						"description": "Terms retrieved successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "object",
										"properties": {
											"name": {
												"type": "string"
											},
											"tid": {
												"type": "string"
											}
										}
									}
								},
								"example": [
									{
										"name": "Preference 1",
										"tid": "301"
									},
									{
										"name": "Preference 2",
										"tid": "302"
									}
								]
							}
						}
					}
				}
			}
		},
		"/api/v1/terms/personality": {
			"get": {
				"tags": ["Taxonomy"],
				"summary": "Get Personality Terms",
				"description": "Retrieve available personality trait options with their term IDs.",
				"operationId": "getPersonalityTerms",
				"responses": {
					"200": {
						"description": "Terms retrieved successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "object",
										"properties": {
											"name": {
												"type": "string"
											},
											"tid": {
												"type": "string"
											}
										}
									}
								},
								"example": [
									{
										"name": "Adventurous",
										"tid": "120"
									},
									{
										"name": "Creative",
										"tid": "121"
									}
								]
							}
						}
					}
				}
			}
		},
		"/api/v1/terms/traits": {
			"get": {
				"tags": ["Taxonomy"],
				"summary": "Get Traits Terms",
				"description": "Retrieve available traits and characteristics with their term IDs.",
				"operationId": "getTraitsTerms",
				"responses": {
					"200": {
						"description": "Terms retrieved successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "object",
										"properties": {
											"name": {
												"type": "string"
											},
											"tid": {
												"type": "string"
											}
										}
									}
								},
								"example": [
									{
										"name": "Confident",
										"tid": "401"
									},
									{
										"name": "Friendly",
										"tid": "402"
									}
								]
							}
						}
					}
				}
			}
		},
		"/api/v1/characters/generate": {
			"post": {
				"tags": ["Characters"],
				"summary": "Generate AI Character",
				"description": "Generates a new AI character in Postgres, creates S3 pre-signed URLs, and triggers ComfyUI reference image generation. Required fields: nationality, ethnicity, firstName, lastName, biography.",
				"deprecated": true,
				"operationId": "generateAiCharacter",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/generate', {\n  method: 'POST',\n  headers: {\n    'Content-Type': 'application/json',\n    'x-api-key': 'YOUR_API_KEY'\n  },\n  body: JSON.stringify({\n    // Required fields\n    nationality: 'American',\n    ethnicity: 'Caucasian / White',\n    firstName: 'Emma',\n    lastName: 'Rose',\n    biography: 'A fun-loving college student who enjoys gaming and cosplay.',\n    // Optional fields\n    b2bClientId: 'your-b2b-client-id',\n    ohFunCreatorId: 'your-ohfun-creator-id',\n    dateOfBirth: '1995-06-15',\n    alias: 'EmmaGamer',\n    job: 'Part-time streamer',\n    whereYouLive: 'Los Angeles, CA',\n    gender: 'Female',\n    orientation: 'Bisexual',\n    sexualExperience: 'Experienced',\n    kinks: ['Roleplay', 'Light bondage', 'Praise kink'],\n    sexPositions: ['Cowgirl', 'Doggy style', 'Missionary'],\n    relationshipStyle: 'Monogamous girlfriend',\n    relationshipStatus: 'Single and looking',\n    interests: ['Gaming', 'Cosplay', 'Anime', 'Streaming'],\n    personality: 'Bubbly / genki',\n    sexDrive: 'High',\n    conversationStyle: 'Flirty & teasing',\n    attitude: 'Bubbly & cheerful',\n    height: \"5'6\\\"\",\n    bodyBuild: 'Slim',\n    bodyShape: 'Hourglass',\n    buttSize: 'Bubble butt',\n    hairLength: 'Long (mid-back to waist)',\n    hairColour: 'Blonde',\n    eyeColour: 'Blue',\n    skinTone: 'Light / Fair',\n    breastSize: 'C cup',\n    breastPertness: 'Perky',\n    nippleColour: 'Pink',\n    vaginaHair: 'Completely shaved / bald',\n    vaginaSize: 'Tight'\n  })\n});\n\nif (!response.ok) {\n  throw new Error(`Request failed: ${response.status}`);\n}\n\nconst data = await response.json();\nconsole.log('Character ID:', data.characterId);"
					},
					{
						"lang": "cURL",
						"source": "curl -X POST 'https://api.oh.xyz/api/v1/characters/generate' \\\n  -H 'Content-Type: application/json' \\\n  -H 'x-api-key: YOUR_API_KEY' \\\n  -d '{\n    \"nationality\": \"American\",\n    \"ethnicity\": \"Caucasian / White\",\n    \"firstName\": \"Emma\",\n    \"lastName\": \"Rose\",\n    \"biography\": \"A fun-loving college student who enjoys gaming and cosplay.\",\n    \"b2bClientId\": \"your-b2b-client-id\",\n    \"ohFunCreatorId\": \"your-ohfun-creator-id\",\n    \"dateOfBirth\": \"1995-06-15\",\n    \"alias\": \"EmmaGamer\",\n    \"job\": \"Part-time streamer\",\n    \"whereYouLive\": \"Los Angeles, CA\",\n    \"gender\": \"Female\",\n    \"orientation\": \"Bisexual\",\n    \"sexualExperience\": \"Experienced\",\n    \"kinks\": [\"Roleplay\", \"Light bondage\", \"Praise kink\"],\n    \"sexPositions\": [\"Cowgirl\", \"Doggy style\", \"Missionary\"],\n    \"relationshipStyle\": \"Monogamous girlfriend\",\n    \"relationshipStatus\": \"Single and looking\",\n    \"interests\": [\"Gaming\", \"Cosplay\", \"Anime\", \"Streaming\"],\n    \"personality\": \"Bubbly / genki\",\n    \"sexDrive\": \"High\",\n    \"conversationStyle\": \"Flirty & teasing\",\n    \"attitude\": \"Bubbly & cheerful\",\n    \"height\": \"5\\'6\\\"\",\n    \"bodyBuild\": \"Slim\",\n    \"bodyShape\": \"Hourglass\",\n    \"buttSize\": \"Bubble butt\",\n    \"hairLength\": \"Long (mid-back to waist)\",\n    \"hairColour\": \"Blonde\",\n    \"eyeColour\": \"Blue\",\n    \"skinTone\": \"Light / Fair\",\n    \"breastSize\": \"C cup\",\n    \"breastPertness\": \"Perky\",\n    \"nippleColour\": \"Pink\",\n    \"vaginaHair\": \"Completely shaved / bald\",\n    \"vaginaSize\": \"Tight\"\n  }'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/generate'\nheaders = {\n    'Content-Type': 'application/json',\n    'x-api-key': 'YOUR_API_KEY'\n}\ndata = {\n    # Required fields\n    'nationality': 'American',\n    'ethnicity': 'Caucasian / White',\n    'firstName': 'Emma',\n    'lastName': 'Rose',\n    'biography': 'A fun-loving college student who enjoys gaming and cosplay.',\n    # Optional fields\n    'b2bClientId': 'your-b2b-client-id',\n    'ohFunCreatorId': 'your-ohfun-creator-id',\n    'dateOfBirth': '1995-06-15',\n    'alias': 'EmmaGamer',\n    'job': 'Part-time streamer',\n    'whereYouLive': 'Los Angeles, CA',\n    'gender': 'Female',\n    'orientation': 'Bisexual',\n    'sexualExperience': 'Experienced',\n    'kinks': ['Roleplay', 'Light bondage', 'Praise kink'],\n    'sexPositions': ['Cowgirl', 'Doggy style', 'Missionary'],\n    'relationshipStyle': 'Monogamous girlfriend',\n    'relationshipStatus': 'Single and looking',\n    'interests': ['Gaming', 'Cosplay', 'Anime', 'Streaming'],\n    'personality': 'Bubbly / genki',\n    'sexDrive': 'High',\n    'conversationStyle': 'Flirty & teasing',\n    'attitude': 'Bubbly & cheerful',\n    'height': '5\\'6\"',\n    'bodyBuild': 'Slim',\n    'bodyShape': 'Hourglass',\n    'buttSize': 'Bubble butt',\n    'hairLength': 'Long (mid-back to waist)',\n    'hairColour': 'Blonde',\n    'eyeColour': 'Blue',\n    'skinTone': 'Light / Fair',\n    'breastSize': 'C cup',\n    'breastPertness': 'Perky',\n    'nippleColour': 'Pink',\n    'vaginaHair': 'Completely shaved / bald',\n    'vaginaSize': 'Tight'\n}\n\nresponse = requests.post(url, headers=headers, json=data)\nresponse.raise_for_status()\nresult = response.json()\nprint('Character ID:', result['characterId'])"
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/GenerateAiCharacterRequest"
							},
							"example": {
								"nationality": "American",
								"ethnicity": "Caucasian / White",
								"firstName": "Emma",
								"lastName": "Rose",
								"biography": "A fun-loving college student who enjoys gaming and cosplay.",
								"b2bClientId": "your-b2b-client-id",
								"ohFunCreatorId": "your-ohfun-creator-id",
								"dateOfBirth": "1995-06-15",
								"alias": "EmmaGamer",
								"job": "Part-time streamer",
								"whereYouLive": "Los Angeles, CA",
								"gender": "Female",
								"orientation": "Bisexual",
								"sexualExperience": "Experienced",
								"kinks": ["Roleplay", "Light bondage", "Praise kink"],
								"sexPositions": ["Cowgirl", "Doggy style", "Missionary"],
								"relationshipStyle": "Monogamous girlfriend",
								"relationshipStatus": "Single and looking",
								"interests": ["Gaming", "Cosplay", "Anime", "Streaming"],
								"personality": "Bubbly / genki",
								"sexDrive": "High",
								"conversationStyle": "Flirty & teasing",
								"attitude": "Bubbly & cheerful",
								"height": "5'6\"",
								"bodyBuild": "Slim",
								"bodyShape": "Hourglass",
								"buttSize": "Bubble butt",
								"hairLength": "Long (mid-back to waist)",
								"hairColour": "Blonde",
								"eyeColour": "Blue",
								"skinTone": "Light / Fair",
								"breastSize": "C cup",
								"breastPertness": "Perky",
								"nippleColour": "Pink",
								"vaginaHair": "Completely shaved / bald",
								"vaginaSize": "Tight"
							}
						}
					}
				},
				"responses": {
					"201": {
						"description": "AI character generated successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"success": {
											"type": "boolean",
											"description": "Whether the generation was successful"
										},
										"characterId": {
											"type": "string",
											"description": "Unique identifier for the generated character"
										},
										"character": {
											"type": "object",
											"description": "The generated character details",
											"properties": {
												"firstName": {
													"type": "string"
												},
												"lastName": {
													"type": "string"
												},
												"alias": {
													"type": "string"
												},
												"biography": {
													"type": "string"
												},
												"nationality": {
													"type": "string"
												},
												"ethnicity": {
													"type": "string"
												},
												"dateOfBirth": {
													"type": "string"
												},
												"gender": {
													"type": "string"
												},
												"orientation": {
													"type": "string"
												},
												"job": {
													"type": "string"
												},
												"whereYouLive": {
													"type": "string"
												},
												"relationshipStatus": {
													"type": "string"
												},
												"interests": {
													"type": "array",
													"items": {
														"type": "string"
													}
												},
												"personality": {
													"type": "string"
												}
											}
										},
										"images": {
											"type": "object",
											"description": "Generated character images",
											"properties": {
												"nsfw_image": {
													"type": "string",
													"description": "Pre-signed S3 URL for the NSFW image"
												},
												"sfw_image": {
													"type": "string",
													"description": "Pre-signed S3 URL for the SFW image"
												}
											}
										},
										"message": {
											"type": "string",
											"description": "Status message"
										}
									}
								},
								"example": {
									"success": true,
									"characterId": "550e8400-e29b-41d4-a716-446655440000",
									"character": {
										"firstName": "Emma",
										"lastName": "Rose",
										"alias": "EmmaGamer",
										"biography": "A passionate gamer and content creator...",
										"nationality": "American",
										"ethnicity": "Caucasian",
										"dateOfBirth": "1999-06-15",
										"gender": "Female",
										"orientation": "Bisexual",
										"job": "Full-time streamer",
										"whereYouLive": "Los Angeles, CA",
										"relationshipStatus": "Single and looking",
										"interests": ["Gaming", "Cosplay", "Anime"],
										"personality": "Bubbly / genki"
									},
									"images": {
										"nsfw_image": "https://s3.amazonaws.com/bucket/nsfw/...",
										"sfw_image": "https://s3.amazonaws.com/bucket/sfw/..."
									},
									"message": "Character generated successfully"
								}
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/save": {
			"post": {
				"tags": ["Characters"],
				"summary": "Save AI Character to Drupal",
				"description": "Saves an AI character from Postgres to Drupal MySQL and updates the Postgres record with the Drupal ID. The characterId from the generate step is required.",
				"deprecated": true,
				"operationId": "saveAiCharacter",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/save', {\n  method: 'POST',\n  headers: {\n    'Content-Type': 'application/json',\n    'x-api-key': 'YOUR_API_KEY'\n  },\n  body: JSON.stringify({\n    // Required field\n    characterId: '550e8400-e29b-41d4-a716-446655440000',\n    // Optional overrides\n    b2bClientId: 'your-b2b-client-id',\n    ohFunCreatorId: 'your-ohfun-creator-id',\n    firstName: 'Emma',\n    lastName: 'Rose',\n    biography: 'Updated biography for Emma.',\n    alias: 'EmmaGamer',\n    job: 'Full-time streamer',\n    whereYouLive: 'Miami, FL',\n    dateOfBirth: '1999-06-15',\n    gender: 'Female',\n    orientation: 'Bisexual',\n    relationshipStatus: 'Single and looking',\n    interests: ['Gaming', 'Cosplay', 'Anime', 'Beach'],\n    personality: 'Bubbly / genki',\n    typeOfCharacter: 'AI Girlfriend',\n    bodyType: 1,\n    eyeColor: 2,\n    hairColor: 3,\n    messagePrice: 0.50,\n    subscriptionPrice: 9.99,\n    uid: 12345\n  })\n});\n\nif (!response.ok) {\n  throw new Error(`Request failed: ${response.status}`);\n}\n\nconst data = await response.json();\nconsole.log('Drupal ID:', data.drupalId);"
					},
					{
						"lang": "cURL",
						"source": "curl -X POST 'https://api.oh.xyz/api/v1/characters/save' \\\n  -H 'Content-Type: application/json' \\\n  -H 'x-api-key: YOUR_API_KEY' \\\n  -d '{\n    \"characterId\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"b2bClientId\": \"your-b2b-client-id\",\n    \"ohFunCreatorId\": \"your-ohfun-creator-id\",\n    \"firstName\": \"Emma\",\n    \"lastName\": \"Rose\",\n    \"biography\": \"Updated biography for Emma.\",\n    \"alias\": \"EmmaGamer\",\n    \"job\": \"Full-time streamer\",\n    \"whereYouLive\": \"Miami, FL\",\n    \"dateOfBirth\": \"1999-06-15\",\n    \"gender\": \"Female\",\n    \"orientation\": \"Bisexual\",\n    \"relationshipStatus\": \"Single and looking\",\n    \"interests\": [\"Gaming\", \"Cosplay\", \"Anime\", \"Beach\"],\n    \"personality\": \"Bubbly / genki\",\n    \"typeOfCharacter\": \"AI Girlfriend\",\n    \"bodyType\": 1,\n    \"eyeColor\": 2,\n    \"hairColor\": 3,\n    \"messagePrice\": 0.50,\n    \"subscriptionPrice\": 9.99,\n    \"uid\": 12345\n  }'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/save'\nheaders = {\n    'Content-Type': 'application/json',\n    'x-api-key': 'YOUR_API_KEY'\n}\ndata = {\n    # Required field\n    'characterId': '550e8400-e29b-41d4-a716-446655440000',\n    # Optional overrides\n    'b2bClientId': 'your-b2b-client-id',\n    'ohFunCreatorId': 'your-ohfun-creator-id',\n    'firstName': 'Emma',\n    'lastName': 'Rose',\n    'biography': 'Updated biography for Emma.',\n    'alias': 'EmmaGamer',\n    'job': 'Full-time streamer',\n    'whereYouLive': 'Miami, FL',\n    'dateOfBirth': '1999-06-15',\n    'gender': 'Female',\n    'orientation': 'Bisexual',\n    'relationshipStatus': 'Single and looking',\n    'interests': ['Gaming', 'Cosplay', 'Anime', 'Beach'],\n    'personality': 'Bubbly / genki',\n    'typeOfCharacter': 'AI Girlfriend',\n    'bodyType': 1,\n    'eyeColor': 2,\n    'hairColor': 3,\n    'messagePrice': 0.50,\n    'subscriptionPrice': 9.99,\n    'uid': 12345\n}\n\nresponse = requests.post(url, headers=headers, json=data)\nresponse.raise_for_status()\nresult = response.json()\nprint('Drupal ID:', result['drupalId'])"
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/SaveAiCharacterRequest"
							},
							"example": {
								"characterId": "550e8400-e29b-41d4-a716-446655440000",
								"b2bClientId": "your-b2b-client-id",
								"ohFunCreatorId": "your-ohfun-creator-id",
								"firstName": "Emma",
								"lastName": "Rose",
								"biography": "Updated biography for Emma.",
								"alias": "EmmaGamer",
								"job": "Full-time streamer",
								"whereYouLive": "Miami, FL",
								"dateOfBirth": "1999-06-15",
								"gender": "Female",
								"orientation": "Bisexual",
								"relationshipStatus": "Single and looking",
								"interests": ["Gaming", "Cosplay", "Anime", "Beach"],
								"personality": "Bubbly / genki",
								"typeOfCharacter": "AI Girlfriend",
								"bodyType": 1,
								"eyeColor": 2,
								"hairColor": 3,
								"messagePrice": 0.5,
								"subscriptionPrice": 9.99,
								"uid": 12345
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Character saved to Drupal successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"success": {
											"type": "boolean",
											"description": "Whether the save was successful"
										},
										"characterId": {
											"type": "number",
											"description": "The character ID in Drupal"
										},
										"message": {
											"type": "string",
											"description": "Status message"
										}
									}
								},
								"example": {
									"success": true,
									"characterId": 12345,
									"message": "Character saved successfully"
								}
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					},
					"404": {
						"description": "Character not found in Postgres"
					}
				}
			}
		},

		"/api/v1/characters/customer-characters": {
			"get": {
				"tags": ["Customer Library"],
				"summary": "Get characters for the authenticated customer",
				"description": "Returns all saved characters for the authenticated customer, with SFW image URLs",
				"operationId": "getCustomerCharacters",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/customer-characters', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/customer-characters' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/customer-characters'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "Customer characters returned successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"success": {
											"type": "boolean"
										},
										"characters": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"cid": {
														"type": "number",
														"description": "Character ID"
													},
													"firstName": {
														"type": "string"
													},
													"lastName": {
														"type": "string"
													},
													"gender": {
														"type": "string"
													},
													"job": {
														"type": "string"
													},
													"image_url": {
														"type": "string",
														"description": "Signed image URL"
													}
												}
											}
										}
									}
								},
								"example": {
									"success": true,
									"characters": [
										{
											"cid": 8671,
											"firstName": "Rachel",
											"lastName": "Walton",
											"gender": "Female",
											"job": "Model",
											"image_url": "<signed-s3-url>"
										}
									]
								}
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},

		"/api/v1/digital-twins/customer-digital-twins": {
			"get": {
				"tags": ["Customer Library"],
				"summary": "Get digital twins for the authenticated customer",
				"description": "Returns all saved digital twins for the authenticated customer",
				"operationId": "getCustomerDigitalTwins",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/digital-twins/customer-digital-twins', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/digital-twins/customer-digital-twins' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/digital-twins/customer-digital-twins'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "Customer digital twins returned successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"success": {
											"type": "boolean"
										},
										"digitalTwins": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"format": "uuid"
													},
													"characterId": {
														"type": "number"
													},
													"name": {
														"type": "string"
													},
													"alias": {
														"type": "string"
													},
													"status": {
														"type": "string"
													},
													"profilePhotoUrl": {
														"type": "string",
														"description": "Signed image URL"
													}
												}
											}
										}
									}
								},
								"example": {
									"success": true,
									"digitalTwins": [
										{
											"id": "a47a1352-af8a-4e4a-9b2f-1e9a180ef984",
											"characterId": 9371,
											"name": "Lisa Stunner",
											"alias": "lisa",
											"status": "active",
											"profilePhotoUrl": "<signed-s3-url>"
										}
									]
								}
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},

		"/api/v1/customer-library": {
			"get": {
				"tags": ["Customer Library"],
				"summary": "Get the authenticated customer's character library",
				"description": "Returns all saved characters and digital twins for the authenticated customer",
				"operationId": "getCustomerLibrary",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/customer-library', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/customer-library' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/customer-library'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "Customer library returned successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"success": {
											"type": "boolean"
										},
										"characters": {
											"type": "array",
											"items": {
												"type": "object"
											}
										},
										"digitalTwins": {
											"type": "array",
											"items": {
												"type": "object"
											}
										}
									}
								},
								"example": {
									"success": true,
									"characters": [
										{
											"cid": 8671,
											"firstName": "Rachel",
											"lastName": "Walton",
											"image_url": "<signed-s3-url>"
										}
									],
									"digitalTwins": [
										{
											"id": "a47a1352-af8a-4e4a-9b2f-1e9a180ef984",
											"characterId": 9371,
											"name": "Lisa Stunner",
											"alias": "lisa",
											"status": "active",
											"profilePhotoUrl": "<signed-s3-url>"
										}
									]
								}
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/api-characters/{b2bClientId}": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get API characters for a B2B client",
				"description": "Returns all characters that have been saved to Drupal for a specific B2B client, with SFW image URLs",
				"deprecated": true,
				"operationId": "fetchB2bClientCharacters",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/api-characters/your-b2b-client-id', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/api-characters/your-b2b-client-id' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/api-characters/your-b2b-client-id'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"parameters": [
					{
						"name": "b2bClientId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string"
						},
						"description": "The B2B client ID to fetch characters for"
					}
				],
				"responses": {
					"200": {
						"description": "List of characters with SFW images",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "object",
										"properties": {
											"cid": {
												"type": "string",
												"description": "Character ID"
											},
											"name": {
												"type": "string",
												"description": "Character name"
											},
											"gender": {
												"type": "string",
												"description": "Character gender"
											},
											"job": {
												"type": "string",
												"description": "Character job"
											},
											"dateOfBirth": {
												"type": "string",
												"format": "date",
												"description": "Date of birth in YYYY-MM-DD format (optional)"
											},
											"level": {
												"type": "string",
												"description": "Character level"
											},
											"body_type": {
												"type": "string",
												"description": "Body type"
											},
											"breast_size": {
												"type": "string",
												"description": "Breast size (for female characters)"
											},
											"ethnicity": {
												"type": "string",
												"description": "Character ethnicity"
											},
											"eye_color": {
												"type": "string",
												"description": "Eye color"
											},
											"hair_length": {
												"type": "string",
												"description": "Hair length"
											},
											"hair_color": {
												"type": "string",
												"description": "Hair color"
											},
											"orientation": {
												"type": "string",
												"description": "Sexual orientation"
											},
											"kinks": {
												"type": "array",
												"items": {
													"type": "string"
												},
												"description": "List of kinks"
											},
											"personality": {
												"type": "array",
												"items": {
													"type": "string"
												},
												"description": "Personality traits with descriptions"
											},
											"management": {
												"type": "object",
												"properties": {
													"max_daily_text": {
														"type": "number",
														"description": "Maximum daily text messages"
													},
													"max_daily_image": {
														"type": "number",
														"description": "Maximum daily images"
													}
												}
											},
											"image": {
												"type": "object",
												"properties": {
													"loras": {
														"type": "object",
														"description": "LoRA model weights"
													},
													"model": {
														"type": "string",
														"description": "Image generation model (flux or sd)"
													}
												}
											},
											"image_url": {
												"type": "string",
												"description": "Profile image URL"
											},
											"character_type": {
												"type": "string",
												"description": "Type of character (e.g., original)"
											}
										}
									}
								},
								"example": [
									{
										"cid": "1",
										"name": "Ava",
										"gender": "Female",
										"job": "Model",
										"dateOfBirth": "2003-05-10",
										"level": "L3",
										"body_type": "Athletic",
										"breast_size": "Large",
										"ethnicity": "Caucasian",
										"eye_color": "Green",
										"hair_length": "Medium",
										"hair_color": "Ginger",
										"orientation": "Straight",
										"kinks": ["Being dominated", "Office romance", "Group sex"],
										"personality": [
											"Charming: Sprinkle in compliments that make {user} feel special.",
											"Witty: Responses should be clever, concise, and surprising.",
											"Subordinate: Responses should be respectful and deferential."
										],
										"management": {
											"max_daily_text": 3,
											"max_daily_image": 3
										},
										"image": {
											"loras": {
												"female_face_1": 0.75,
												"female_face_2": 0.75
											},
											"model": "flux"
										},
										"image_url": "https://d3mpf1svyo6ceu.cloudfront.net/char-profile-pics/screenshot-2025-03-07-at-15.52.23-large.png",
										"character_type": "original"
									}
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/{characterId}": {
			"patch": {
				"tags": ["Characters"],
				"summary": "Update Character",
				"description": "Partially update an existing character's attributes. Only include fields you want to change.",
				"operationId": "updateCharacter",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/737', {\n  method: 'PATCH',\n  headers: {\n    'Content-Type': 'application/json',\n    'x-api-key': 'YOUR_API_KEY'\n  },\n  body: JSON.stringify({\n    firstName: 'Sophia',\n    lastName: 'Martinez',\n    biography: 'A creative artist from Barcelona',\n    dateOfBirth: '1995-06-15'\n  })\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X PATCH 'https://api.oh.xyz/api/v1/characters/737' \\\n  -H 'Content-Type: application/json' \\\n  -H 'x-api-key: YOUR_API_KEY' \\\n  -d '{\n    \"firstName\": \"Sophia\",\n    \"lastName\": \"Martinez\",\n    \"biography\": \"A creative artist from Barcelona\",\n    \"dateOfBirth\": \"1995-06-15\"\n  }'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/737'\nheaders = {\n    'Content-Type': 'application/json',\n    'x-api-key': 'YOUR_API_KEY'\n}\ndata = {\n    'firstName': 'Sophia',\n    'lastName': 'Martinez',\n    'biography': 'A creative artist from Barcelona',\n    'dateOfBirth': '1995-06-15'\n}\n\nresponse = requests.patch(url, headers=headers, json=data)\nprint(response.json())"
					}
				],
				"parameters": [
					{
						"name": "characterId",
						"in": "path",
						"required": true,
						"description": "Unique identifier of the character (integer or UUID)",
						"schema": {
							"type": "string"
						},
						"examples": {
							"integer": {
								"value": "737",
								"summary": "Integer ID"
							},
							"uuid": {
								"value": "123e4567-e89b-12d3-a456-426614174000",
								"summary": "UUID"
							}
						}
					}
				],
				"requestBody": {
					"required": true,
					"description": "Character fields to update (all optional)",
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"description": "Character fields to update. All fields are optional. Only include fields you want to change. Enum fields must use values from the allowed list — use the taxonomy endpoints to retrieve valid options.",
								"properties": {
									"firstName": {
										"type": "string",
										"description": "Character's first name",
										"example": "Sophia"
									},
									"lastName": {
										"type": "string",
										"description": "Character's last name",
										"example": "Martinez"
									},
									"biography": {
										"type": "string",
										"description": "Character biography/backstory",
										"example": "A creative artist from Barcelona with a passion for modern art"
									},
									"alias": {
										"type": "string",
										"description": "Character alias/nickname",
										"example": "Sophie"
									},
									"job": {
										"type": "string",
										"description": "Job title",
										"example": "Photographer"
									},
									"whereYouLive": {
										"type": "string",
										"description": "Location where character lives",
										"example": "Barcelona, Spain"
									},
									"dateOfBirth": {
										"type": "string",
										"format": "date",
										"description": "Date of birth (ISO format: YYYY-MM-DD). Age will be calculated and must be 21+",
										"example": "1995-06-15"
									},
									"gender": {
										"type": "string",
										"description": "Character gender",
										"example": "Female"
									},
									"nationality": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/nationality"
									},
									"ethnicity": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/ethnicity"
									},
									"height": {
										"type": "string",
										"description": "Character height",
										"example": "5'7\""
									},
									"bodyBuild": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/bodyBuild"
									},
									"bodyShape": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/bodyShape"
									},
									"buttSize": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/buttSize"
									},
									"hairLength": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/hairLength"
									},
									"hairColour": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/hairColour"
									},
									"eyeColour": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/eyeColour"
									},
									"skinTone": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/skinTone"
									},
									"breastSize": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/breastSize"
									},
									"breastPertness": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/breastPertness"
									},
									"nippleColour": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/nippleColour"
									},
									"vaginaHair": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/vaginaHair"
									},
									"vaginaSize": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/vaginaSize"
									},
									"orientation": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/orientation"
									},
									"sexualExperience": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/sexualExperience"
									},
									"kinks": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/kinks"
									},
									"sexPositions": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/sexPositions"
									},
									"relationshipStyle": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/relationshipStyle"
									},
									"relationshipStatus": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/relationshipStatus"
									},
									"interests": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/interests"
									},
									"personality": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/personality"
									},
									"sexDrive": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/sexDrive"
									},
									"conversationStyle": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/conversationStyle"
									},
									"attitude": {
										"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/attitude"
									},
									"voiceId": {
										"type": "string",
										"description": "S3 URI for the voice reference audio",
										"example": "s3://oh-chatterbox-audio/abc123/reference.wav"
									},
									"audioSpeed": {
										"type": "number",
										"description": "Audio playback speed multiplier (0.5-2.0)",
										"minimum": 0.5,
										"maximum": 2.0,
										"default": 0.5,
										"example": 1.0
									}
								}
							},
							"examples": {
								"basicUpdate": {
									"summary": "Update name only",
									"value": {
										"firstName": "Sophia",
										"lastName": "Martinez"
									}
								},
								"physicalUpdate": {
									"summary": "Update physical attributes",
									"value": {
										"hairColour": "Blonde",
										"eyeColour": "Blue",
										"bodyBuild": "Athletic",
										"skinTone": "Light / Fair"
									}
								},
								"personalityUpdate": {
									"summary": "Update personality and preferences",
									"value": {
										"personality": "Flirty tease",
										"attitude": "Confident boss babe",
										"conversationStyle": "Flirty & teasing",
										"interests": ["Painting", "Travel", "Music"],
										"kinks": ["Roleplay", "Light bondage"]
									}
								},
								"voiceUpdate": {
									"summary": "Update voice settings",
									"value": {
										"voiceId": "s3://oh-chatterbox-audio/abc123/reference.wav",
										"audioSpeed": 1.0
									}
								},
								"fullUpdate": {
									"summary": "Update multiple fields",
									"value": {
										"firstName": "Sophia",
										"lastName": "Martinez",
										"biography": "A creative artist from Barcelona",
										"dateOfBirth": "1995-06-15",
										"nationality": "Spanish",
										"ethnicity": "Mediterranean (Greek/Italian/Spanish)",
										"hairColour": "Dark Brown",
										"eyeColour": "Brown / Dark Brown",
										"personality": "Confident boss babe",
										"interests": ["Painting", "Travel", "Music"]
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Character updated successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"required": [
										"success",
										"characterId",
										"updatedFields",
										"message"
									],
									"properties": {
										"success": {
											"type": "boolean",
											"description": "Indicates if the update was successful",
											"example": true
										},
										"characterId": {
											"type": "integer",
											"description": "ID of the updated character",
											"example": 737
										},
										"updatedFields": {
											"type": "array",
											"items": {
												"type": "string"
											},
											"description": "List of field names that were modified",
											"example": ["firstName", "lastName", "age"]
										},
										"message": {
											"type": "string",
											"description": "Human-readable success message",
											"example": "Character updated successfully. 3 field(s) modified."
										}
									}
								},
								"example": {
									"success": true,
									"characterId": 737,
									"updatedFields": ["firstName", "lastName"],
									"message": "Character updated successfully. 2 field(s) modified."
								}
							}
						}
					},
					"400": {
						"description": "Bad request - Invalid input",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"statusCode": {
											"type": "integer",
											"description": "HTTP status code",
											"example": 400
										},
										"message": {
											"type": "string",
											"description": "Error message",
											"example": "Validation failed: dateOfBirth must be a valid ISO date string"
										},
										"error": {
											"type": "string",
											"description": "Error type",
											"example": "Bad Request"
										}
									}
								}
							}
						}
					},
					"401": {
						"description": "Unauthorized - Missing or invalid API key",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"message": {
											"type": "string",
											"example": "Unauthorized"
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "Forbidden - Insufficient permissions",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"Message": {
											"type": "string",
											"example": "User is not authorized to access this resource"
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "Character not found",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"statusCode": {
											"type": "integer",
											"example": 404
										},
										"message": {
											"type": "string",
											"example": "Character with ID 999 not found"
										},
										"error": {
											"type": "string",
											"example": "Not Found"
										}
									}
								}
							}
						}
					},
					"500": {
						"description": "Internal server error",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"statusCode": {
											"type": "integer",
											"example": 500
										},
										"message": {
											"type": "string",
											"example": "Internal server error"
										},
										"error": {
											"type": "string",
											"example": "Internal Server Error"
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/api/v1/characters/nationalities": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed nationalities",
				"description": "Returns the list of allowed nationality values for character generation",
				"operationId": "getNationalities",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/nationalities', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/nationalities' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/nationalities'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of nationalities",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"American",
									"Brazilian",
									"Russian",
									"Colombian",
									"Ukrainian",
									"Japanese"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/ethnicities": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed ethnicities",
				"description": "Returns the list of allowed ethnicity values for character generation",
				"operationId": "getEthnicities",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/ethnicities', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/ethnicities' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/ethnicities'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of ethnicities",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Caucasian / White",
									"Latina / Hispanic",
									"Black / African-American",
									"Asian (East Asian)"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/hair-lengths": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed hair lengths",
				"description": "Returns the list of allowed hair length values for character generation",
				"operationId": "getHairLengths",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/hair-lengths', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/hair-lengths' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/hair-lengths'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of hair lengths",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Long (mid-back to waist)",
									"Shoulder-length",
									"Short bob",
									"Pixie cut"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/hair-colours": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed hair colours",
				"description": "Returns the list of allowed hair colour values for character generation",
				"operationId": "getHairColours",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/hair-colours', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/hair-colours' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/hair-colours'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of hair colours",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Black",
									"Dark Brown",
									"Blonde",
									"Platinum Blonde",
									"Natural Red / Ginger"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/eye-colours": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed eye colours",
				"description": "Returns the list of allowed eye colour values for character generation",
				"operationId": "getEyeColours",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/eye-colours', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/eye-colours' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/eye-colours'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of eye colours",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Blue",
									"Green",
									"Brown / Dark Brown",
									"Hazel",
									"Grey"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/skin-tones": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed skin tones",
				"description": "Returns the list of allowed skin tone values for character generation",
				"operationId": "getSkinTones",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/skin-tones', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/skin-tones' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/skin-tones'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of skin tones",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Light / Fair",
									"Tan / Golden",
									"Olive / Mediterranean",
									"Caramel / Light Brown"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/breast-sizes": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed breast sizes",
				"description": "Returns the list of allowed breast size values for character generation",
				"operationId": "getBreastSizes",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/breast-sizes', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/breast-sizes' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/breast-sizes'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of breast sizes",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"C cup",
									"D cup",
									"DD / E cup",
									"B cup",
									"Natural C"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/nipple-colours": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed nipple colours",
				"description": "Returns the list of allowed nipple colour values for character generation",
				"operationId": "getNippleColours",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/nipple-colours', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/nipple-colours' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/nipple-colours'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of nipple colours",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Light pink",
									"Pink",
									"Rose / Medium pink",
									"Dark pink",
									"Light brown"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/breast-pertness": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed breast pertness options",
				"description": "Returns the list of allowed breast pertness values for character generation",
				"operationId": "getBreastPertness",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/breast-pertness', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/breast-pertness' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/breast-pertness'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of breast pertness options",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Perky",
									"Firm & perky",
									"Natural perky",
									"Teardrop shape"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/vagina-hair": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed vagina hair styles",
				"description": "Returns the list of allowed vagina hair style values for character generation",
				"operationId": "getVaginaHair",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/vagina-hair', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/vagina-hair' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/vagina-hair'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of vagina hair styles",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Completely shaved / bald",
									"Landing strip",
									"Neat triangle",
									"Natural but shaped"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/vagina-sizes": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed vagina sizes",
				"description": "Returns the list of allowed vagina size values for character generation",
				"operationId": "getVaginaSizes",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/vagina-sizes', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/vagina-sizes' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/vagina-sizes'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of vagina sizes",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Tight",
									"Very tight",
									"Snug",
									"Average",
									"Petite tight"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/body-builds": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed body builds",
				"description": "Returns the list of allowed body build values for character generation",
				"operationId": "getBodyBuilds",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/body-builds', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/body-builds' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/body-builds'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of body builds",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Slim",
									"Petite",
									"Athletic",
									"Curvy",
									"Hourglass",
									"Fit"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/body-shapes": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed body shapes",
				"description": "Returns the list of allowed body shape values for character generation",
				"operationId": "getBodyShapes",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/body-shapes', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/body-shapes' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/body-shapes'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of body shapes",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Hourglass",
									"Pear",
									"Apple",
									"Rectangle",
									"Slim hourglass"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/butt-sizes": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed butt sizes",
				"description": "Returns the list of allowed butt size values for character generation",
				"operationId": "getButtSizes",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/butt-sizes', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/butt-sizes' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/butt-sizes'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of butt sizes",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Big",
									"Round & full",
									"Bubble butt",
									"Thick",
									"Jiggly"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/orientations": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed orientations",
				"description": "Returns the list of allowed sexual orientation values for character generation",
				"operationId": "getOrientations",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/orientations', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/orientations' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/orientations'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of orientations",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Straight",
									"Bisexual",
									"Mostly straight",
									"Bi-curious",
									"Pansexual"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/sexual-experiences": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed sexual experiences",
				"description": "Returns the list of allowed sexual experience values for character generation",
				"operationId": "getSexualExperiences",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/sexual-experiences', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/sexual-experiences' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/sexual-experiences'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of sexual experiences",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Virgin",
									"Almost virgin",
									"Moderate",
									"Experienced",
									"Very experienced"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/kinks": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed kinks",
				"description": "Returns the list of allowed kink values for character generation",
				"operationId": "getKinks",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/kinks', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/kinks' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/kinks'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of kinks",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Daddy kink / DDLG",
									"Light bondage",
									"Spanking",
									"Praise kink",
									"Roleplay"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/sex-positions": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed sex positions",
				"description": "Returns the list of allowed sex position values for character generation",
				"operationId": "getSexPositions",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/sex-positions', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/sex-positions' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/sex-positions'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of sex positions",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Doggy style",
									"Missionary",
									"Cowgirl",
									"Reverse cowgirl",
									"Spooning"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/relationship-styles": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed relationship styles (Drupal)",
				"description": "Returns the list of allowed relationship style values for Drupal characters",
				"operationId": "getRelationshipStyles",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/relationship-styles', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/relationship-styles' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/relationship-styles'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of relationship styles",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Monogamous girlfriend",
									"Devoted housewife",
									"Open relationship",
									"Casual FWB"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/personalities": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed personalities",
				"description": "Returns the list of allowed personality values for character generation",
				"operationId": "getPersonalities",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/personalities', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/personalities' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/personalities'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of personalities",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Sweet & innocent",
									"Bratty",
									"Bubbly / genki",
									"Shy & submissive",
									"Flirty tease"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/sex-drives": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed sex drives",
				"description": "Returns the list of allowed sex drive values for character generation",
				"operationId": "getSexDrives",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/sex-drives', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/sex-drives' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/sex-drives'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of sex drives",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": ["Very high", "High", "Average", "Moderate", "Low"]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/conversation-styles": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed conversation styles",
				"description": "Returns the list of allowed conversation style values for character generation",
				"operationId": "getConversationStyles",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/conversation-styles', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/conversation-styles' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/conversation-styles'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of conversation styles",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Flirty & teasing",
									"Sweet & innocent",
									"Bratty & sassy",
									"Shy & soft-spoken"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/attitudes": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed attitudes",
				"description": "Returns the list of allowed attitude values for character generation",
				"operationId": "getAttitudes",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/attitudes', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/attitudes' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/attitudes'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of attitudes",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Sweet & innocent",
									"Bratty",
									"Bubbly & cheerful",
									"Shy & submissive",
									"Flirty tease"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/relationship-statuses": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed relationship statuses",
				"description": "Returns the list of allowed relationship status values for character generation",
				"operationId": "getRelationshipStatuses",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/relationship-statuses', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/relationship-statuses' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/relationship-statuses'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of relationship statuses",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Single",
									"In a relationship",
									"Engaged",
									"Married",
									"It's complicated"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/interests": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get allowed interests",
				"description": "Returns the list of allowed interest values for character generation",
				"operationId": "getInterests",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/interests', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/interests' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/interests'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of interests",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"Dancing",
									"Yoga",
									"Gaming",
									"Cosplay",
									"Anime",
									"Hiking",
									"Music",
									"Gym"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/characters/jobs": {
			"get": {
				"tags": ["Characters"],
				"summary": "Get job suggestions",
				"description": "Returns a list of suggested job titles for character generation. The job field accepts free text, so any value is valid.",
				"operationId": "getJobs",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v1/characters/jobs', {\n  method: 'GET',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY'\n  }\n});\n\nconst data = await response.json();\nconsole.log(data);"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/characters/jobs' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v1/characters/jobs'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY'\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())"
					}
				],
				"responses": {
					"200": {
						"description": "List of job suggestions",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "string"
									}
								},
								"example": [
									"College student",
									"Content creator",
									"Nurse",
									"Flight attendant",
									"Social media influencer",
									"Barista",
									"Personal assistant",
									"Fitness trainer",
									"Teacher",
									"Yoga instructor",
									"Server",
									"Dancer",
									"Cosplayer",
									"Model",
									"Corporate professional",
									"Artist",
									"Cheerleader",
									"Gamer / streamer",
									"Photographer",
									"Software engineer",
									"Marketing manager",
									"Designer",
									"Real estate agent",
									"Entrepreneur",
									"Hair stylist",
									"Makeup artist",
									"Event planner",
									"Travel blogger",
									"Chef",
									"Actress",
									"Singer",
									"Writer",
									"Journalist",
									"Lawyer",
									"Doctor",
									"Veterinarian",
									"Psychologist",
									"Architect",
									"Interior designer",
									"Fashion designer",
									"Musician",
									"DJ",
									"Bartender",
									"Lifeguard",
									"Massage therapist",
									"Nail technician",
									"Personal shopper",
									"Tour guide",
									"Pilot",
									"Marine biologist"
								]
							}
						}
					},
					"401": {
						"description": "Unauthorized - invalid API key"
					}
				}
			}
		},
		"/api/v1/videos/create": {
			"post": {
				"tags": ["Videos"],
				"summary": "Generate Video (Async)",
				"description": "Generate a video asynchronously. Supports two modes:\n\n**Image-to-Video (i2v):** Provide an `imageUrl` and a `category` to animate an existing image with a specific motion/position. You must also provide a `videoPath` for the output location. Poll `GET /api/v1/videos/get?videoId={id}` for status.\n\n**Text-to-Video (t2v):** Provide a `character_id` and a `prompt` — the system generates a starting image from the character and animates it automatically. Poll `GET /api/v1/jobs/{job_id}/status` for status.",
				"operationId": "generateVideo",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "// Image-to-Video: animate your own image with a specific position\nconst i2vResponse = await fetch('https://api.oh.xyz/api/v1/videos/create', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY' },\n  body: JSON.stringify({\n    imageUrl: 'https://example.com/my-image.jpg',\n    prompt: 'Riding motion with bouncing',\n    category: 'cowgirl',\n    videoPath: 'videos/my-customer/1234567890-5s.mp4',\n    videoLength: 5\n  })\n});\nconst i2vData = await i2vResponse.json();\n// Poll: GET /api/v1/videos/get?videoId={i2vData.id}\n\n// Text-to-Video: generate from character\nconst t2vResponse = await fetch('https://api.oh.xyz/api/v1/videos/create', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY' },\n  body: JSON.stringify({\n    character_id: '154',\n    prompt: 'Waving at the camera and smiling',\n    length: 5,\n    resolution: '9:16'\n  })\n});\nconst t2vData = await t2vResponse.json();\n// Poll: GET /api/v1/jobs/{t2vData.job_id}/status"
					},
					{
						"lang": "cURL",
						"source": "# Image-to-Video\ncurl -X POST 'https://api.oh.xyz/api/v1/videos/create' \\\n  -H 'Content-Type: application/json' \\\n  -H 'x-api-key: YOUR_API_KEY' \\\n  -d '{\n    \"imageUrl\": \"https://example.com/my-image.jpg\",\n    \"prompt\": \"Riding motion with bouncing\",\n    \"category\": \"cowgirl\",\n    \"videoPath\": \"videos/my-customer/1234567890-5s.mp4\",\n    \"videoLength\": 5\n  }'\n\n# Text-to-Video\ncurl -X POST 'https://api.oh.xyz/api/v1/videos/create' \\\n  -H 'Content-Type: application/json' \\\n  -H 'x-api-key: YOUR_API_KEY' \\\n  -d '{\n    \"character_id\": \"154\",\n    \"prompt\": \"Waving at the camera and smiling\",\n    \"length\": 5,\n    \"resolution\": \"9:16\"\n  }'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\n# Image-to-Video\ni2v = requests.post(\n    'https://api.oh.xyz/api/v1/videos/create',\n    headers={'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY'},\n    json={\n        'imageUrl': 'https://example.com/my-image.jpg',\n        'prompt': 'Riding motion with bouncing',\n        'category': 'cowgirl',\n        'videoPath': 'videos/my-customer/1234567890-5s.mp4',\n        'videoLength': 5\n    }\n).json()\n# Poll: GET /api/v1/videos/get?videoId={i2v['id']}\n\n# Text-to-Video\nt2v = requests.post(\n    'https://api.oh.xyz/api/v1/videos/create',\n    headers={'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY'},\n    json={\n        'character_id': '154',\n        'prompt': 'Waving at the camera and smiling',\n        'length': 5,\n        'resolution': '9:16'\n    }\n).json()\n# Poll: GET /api/v1/jobs/{t2v['job_id']}/status"
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"required": ["prompt"],
								"description": "Two modes are supported depending on which fields you provide. For image-to-video, provide `imageUrl`, `category`, and `videoPath`. For text-to-video, provide `character_id`.",
								"properties": {
									"prompt": {
										"type": "string",
										"minLength": 1,
										"description": "Description of the video content or motion to generate."
									},
									"imageUrl": {
										"type": "string",
										"format": "uri",
										"description": "**Image-to-Video mode.** URL of the source image to animate. Required for i2v mode."
									},
									"category": {
										"type": "string",
										"enum": [
											"blowjob",
											"pov_blowjob",
											"cowgirl",
											"pov_cowgirl",
											"reverse_cowgirl",
											"pov_reverse_cowgirl",
											"pov_missionary",
											"doggy",
											"pov_doggy",
											"cunnilingus",
											"handjob",
											"pov_handjob",
											"foot_job",
											"pov_foot_job",
											"tit_job",
											"standing_up_sex_from_behind"
										],
										"description": "**Image-to-Video mode.** Motion/position category for the video. Determines what kind of motion is applied to the source image. Required for i2v mode."
									},
									"videoPath": {
										"type": "string",
										"description": "**Image-to-Video mode.** S3 storage path for the output video (e.g., `videos/{customerId}/{timestamp}-5s.mp4`). Required for i2v mode."
									},
									"videoLength": {
										"type": "integer",
										"enum": [5, 10, 15],
										"description": "**Image-to-Video mode.** Video length in seconds. Must be 5, 10, or 15."
									},
									"character_id": {
										"type": "string",
										"description": "**Text-to-Video mode.** Character identifier. The system generates a starting image from the character and animates it."
									},
									"image_url": {
										"type": "string",
										"format": "uri",
										"description": "**Text-to-Video mode.** Alternative to character_id — provide your own source image. The system will animate it based on the prompt with automatic action detection."
									},
									"prompt_enhancement": {
										"type": "boolean",
										"default": true,
										"description": "**Text-to-Video mode.** When enabled, the system enhances your prompt using AI to improve video quality and motion. Enabled by default."
									},
									"resolution": {
										"oneOf": [
											{
												"type": "array",
												"items": {
													"type": "integer"
												},
												"minItems": 2,
												"maxItems": 2,
												"description": "Explicit [width, height] in pixels"
											},
											{
												"type": "string",
												"enum": ["9:16", "16:9", "1:1", "4:3", "3:4"],
												"description": "Aspect ratio preset"
											}
										],
										"description": "**Text-to-Video mode.** Output resolution. Can be an aspect ratio string or [width, height] array. Aspect ratio presets map to: 9:16 → 720×1800, 16:9 → 1280×720, 1:1 → 1024×1024, 4:3 → 960×720, 3:4 → 720×960."
									},
									"length": {
										"type": "integer",
										"enum": [5, 10, 15],
										"default": 5,
										"description": "**Text-to-Video mode.** Video length in seconds. Must be 5, 10, or 15."
									}
								}
							},
							"examples": {
								"i2v": {
									"summary": "Image-to-Video: animate an image with a specific position",
									"value": {
										"imageUrl": "https://example.com/my-image.jpg",
										"prompt": "Riding motion with bouncing",
										"category": "cowgirl",
										"videoPath": "videos/my-customer/1234567890-5s.mp4",
										"videoLength": 5
									}
								},
								"t2v_character": {
									"summary": "Text-to-Video: generate from character",
									"value": {
										"character_id": "154",
										"prompt": "Waving at the camera and smiling",
										"length": 5,
										"resolution": "9:16"
									}
								},
								"t2v_image": {
									"summary": "Text-to-Video: animate your image with auto-detected action",
									"value": {
										"image_url": "https://example.com/my-image.jpg",
										"prompt": "Natural movement and motion",
										"length": 10
									}
								}
							}
						}
					}
				},
				"responses": {
					"201": {
						"description": "Video generation job accepted (Image-to-Video mode)",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Video job ID. Poll status with GET /api/v1/videos/get?videoId={id}"
										},
										"status": {
											"type": "string",
											"enum": ["GENERATING"],
											"description": "Initial job status"
										}
									}
								},
								"example": {
									"id": "abc123-def456",
									"status": "GENERATING"
								}
							}
						}
					},
					"202": {
						"description": "Video generation job accepted (Text-to-Video mode)",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"message": {
											"type": "string",
											"description": "Status message"
										},
										"job_id": {
											"type": "string",
											"description": "Job ID. Poll status with GET /api/v1/jobs/{job_id}/status"
										},
										"status": {
											"type": "string",
											"enum": ["processing"],
											"description": "Initial job status"
										},
										"presigned_url": {
											"type": "string",
											"description": "Presigned download URL for the video (valid for 7 days)"
										}
									}
								},
								"example": {
									"message": "Video generation started",
									"job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
									"status": "processing",
									"presigned_url": "https://s3.amazonaws.com/..."
								}
							}
						}
					},
					"400": {
						"description": "Validation error or missing required fields",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"error": {
											"type": "string"
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/api/v2/characters/generate": {
			"post": {
				"tags": ["Characters V2"],
				"summary": "Generate AI Character",
				"description": "Creates a new AI character with async profile generation. Returns immediately with 'generating' status. Poll the status endpoint until status is 'ready'.",
				"operationId": "generateCharacterV2",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "// Example: distinctive accessory + tattoo via additionalDescription.\nconst response = await fetch('https://api.oh.xyz/api/v2/characters/generate', {\n  method: 'POST',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY',\n    'Content-Type': 'application/json'\n  },\n  body: JSON.stringify({\n    firstName: 'Aria',\n    lastName: 'Storm',\n    biography: 'A free-spirited artist with a passion for adventure',\n    nationality: 'American',\n    ethnicity: 'Caucasian',\n    gender: 'Female',\n    // Free-text addendum for the reference image. Rejected values surface as 400.\n    additionalDescription: 'wire-frame glasses, small dragon tattoo across the right hip'\n  })\n});\n\nif (response.status === 400) {\n  const { message } = await response.json();\n  console.error('Moderation:', message); // 'Additional details blocked by moderation'\n  return;\n}\n\nconst data = await response.json();\nconst { characterGuid } = data;\nconsole.log('Character GUID:', characterGuid);"
					},
					{
						"lang": "cURL",
						"source": "# Example: pregnancy state + scar via additionalDescription.\ncurl -X POST 'https://api.oh.xyz/api/v2/characters/generate' \\\n  -H 'x-api-key: YOUR_API_KEY' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\\\n    \"firstName\": \"Aria\",\\\n    \"lastName\": \"Storm\",\\\n    \"biography\": \"A free-spirited artist with a passion for adventure\",\\\n    \"nationality\": \"American\",\\\n    \"ethnicity\": \"Caucasian\",\\\n    \"gender\": \"Female\",\\\n    \"additionalDescription\": \"six months pregnant, a faint scar across her left cheekbone\"\\\n  }'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\n# Example: full-sleeve tattoo + nose piercing via additionalDescription.\nurl = 'https://api.oh.xyz/api/v2/characters/generate'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY',\n    'Content-Type': 'application/json'\n}\npayload = {\n    'firstName': 'Aria',\n    'lastName': 'Storm',\n    'biography': 'A free-spirited artist with a passion for adventure',\n    'nationality': 'American',\n    'ethnicity': 'Caucasian',\n    'gender': 'Female',\n    # Free-text addendum for the reference image. Trimmed; empty values skip moderation.\n    'additionalDescription': 'full sleeve tattoo of cherry blossoms down her left arm, septum nose piercing',\n}\n\nresponse = requests.post(url, headers=headers, json=payload)\nif response.status_code == 400:\n    print('Moderation:', response.json()['message'])\nelse:\n    data = response.json()\n    print('Character GUID:', data['characterGuid'])"
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/GenerateCharacterRequest"
							}
						}
					}
				},
				"responses": {
					"202": {
						"description": "Character generation started. Poll status endpoint for completion.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"success": {
											"type": "boolean",
											"example": true
										},
										"characterGuid": {
											"type": "string",
											"format": "uuid",
											"description": "Character GUID for status polling and save operation"
										},
										"status": {
											"$ref": "#/components/schemas/CharacterStatus"
										},
										"character": {
											"type": "object",
											"description": "Initial character data"
										},
										"images": {
											"type": "object",
											"properties": {
												"nsfw_image": {
													"type": "string",
													"format": "uri"
												},
												"sfw_image": {
													"type": "string",
													"format": "uri"
												}
											}
										},
										"message": {
											"type": "string",
											"example": "Character generation started. Poll GET /api/v2/characters/{characterGuid}/status for progress."
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "Validation error - missing required fields, character under 21, or `additionalDescription` blocked by moderation (response body: `{\"message\": \"Additional details blocked by moderation\"}`)."
					},
					"401": {
						"description": "Unauthorized - invalid or missing API key"
					}
				}
			}
		},
		"/api/v2/characters/{characterGuid}": {
			"get": {
				"tags": ["Characters V2"],
				"summary": "Get Generated Character",
				"description": "Returns the full generated character data including AI-generated profile. Use this after generation is complete (status='ready') to review before saving.",
				"operationId": "getGeneratedCharacter",
				"parameters": [
					{
						"name": "characterGuid",
						"in": "path",
						"required": true,
						"description": "Character GUID from the generate step",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					}
				],
				"responses": {
					"200": {
						"description": "Generated character data retrieved",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"character": {
											"type": "object",
											"description": "Physical attributes, names, bio from character entity"
										},
										"generatedProfile": {
											"type": "object",
											"description": "AI-generated personality, interests, kinks, backstory, prompts"
										},
										"config": {
											"type": "object",
											"description": "User-provided pricing, social handles, media, voice config"
										},
										"images": {
											"type": "object",
											"properties": {
												"nsfw_image": {
													"type": "string",
													"format": "uri"
												},
												"sfw_image": {
													"type": "string",
													"format": "uri"
												}
											}
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "Character not found"
					}
				}
			}
		},
		"/api/v2/characters/save": {
			"post": {
				"tags": ["Characters V2"],
				"summary": "Save AI Character",
				"description": "Saves a generated character. Returns immediately with 'saving' status. Poll status endpoint until status is 'saved'. Any fields provided here override what was generated.",
				"operationId": "saveCharacterV2",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "const response = await fetch('https://api.oh.xyz/api/v2/characters/save', {\n  method: 'POST',\n  headers: {\n    'x-api-key': 'YOUR_API_KEY',\n    'Content-Type': 'application/json'\n  },\n  body: JSON.stringify({\n    characterGuid: 'YOUR_CHARACTER_GUID'\n  })\n});\n\nconst data = await response.json();\nconsole.log('Status:', data.status);"
					},
					{
						"lang": "cURL",
						"source": "curl -X POST 'https://api.oh.xyz/api/v2/characters/save' \\\n  -H 'x-api-key: YOUR_API_KEY' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\\\n    \"characterGuid\": \"YOUR_CHARACTER_GUID\"\\\n  }'"
					},
					{
						"lang": "Python",
						"source": "import requests\n\nurl = 'https://api.oh.xyz/api/v2/characters/save'\nheaders = {\n    'x-api-key': 'YOUR_API_KEY',\n    'Content-Type': 'application/json'\n}\npayload = {\n    'characterGuid': 'YOUR_CHARACTER_GUID'\n}\n\nresponse = requests.post(url, headers=headers, json=payload)\ndata = response.json()\nprint('Status:', data['status'])"
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/SaveCharacterRequest"
							}
						}
					}
				},
				"responses": {
					"202": {
						"description": "Character save started. Poll status endpoint for completion.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"success": {
											"type": "boolean",
											"example": true
										},
										"characterGuid": {
											"type": "string",
											"format": "uuid"
										},
										"status": {
											"$ref": "#/components/schemas/CharacterStatus"
										},
										"message": {
											"type": "string",
											"example": "Character save started. Poll GET /api/v2/characters/{characterGuid}/status for progress."
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "Validation error - missing characterGuid, character under 21, or already saved"
					},
					"401": {
						"description": "Unauthorized - invalid or missing API key"
					},
					"404": {
						"description": "Character not found"
					}
				}
			}
		},
		"/api/v2/characters/{characterGuid}/status": {
			"get": {
				"tags": ["Characters V2"],
				"summary": "Get Character Status",
				"description": "Returns the current processing status of a character. Poll this endpoint after generate/save operations to track progress.",
				"operationId": "getCharacterStatus",
				"parameters": [
					{
						"name": "characterGuid",
						"in": "path",
						"required": true,
						"description": "Character GUID from the generate step",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					}
				],
				"responses": {
					"200": {
						"description": "Character status retrieved",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"characterGuid": {
											"type": "string",
											"format": "uuid"
										},
										"status": {
											"$ref": "#/components/schemas/CharacterStatus"
										},
										"characterId": {
											"type": "integer",
											"description": "Populated when status is 'saved'"
										},
										"errorMessage": {
											"type": "string",
											"nullable": true,
											"description": "Error message if processing failed"
										},
										"createdAt": {
											"type": "string",
											"format": "date-time"
										},
										"updatedAt": {
											"type": "string",
											"format": "date-time"
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "Character not found"
					}
				}
			}
		},
		"/api/v1/digital-twins": {
			"post": {
				"tags": ["Digital Twins"],
				"summary": "Create Digital Twin",
				"description": "Creates a new digital twin character. Processing happens asynchronously. Poll the status endpoint to track completion.",
				"operationId": "createDigitalTwin",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/CreateDigitalTwinRequest"
							},
							"example": {
								"name": "Luna Starfire",
								"alias": "Luna",
								"dateOfBirth": "1998-06-15",
								"job": "Digital Artist",
								"location": "Los Angeles, CA",
								"gender": "Female",
								"orientation": "Bisexual",
								"hairColour": "Blonde",
								"eyeColour": "Blue",
								"bodyType": "Athletic",
								"referenceImageUrl": "https://cdn.example.com/images/reference.jpg",
								"contentLevel": "Sexy",
								"bio": "A creative digital artist who loves exploring new worlds.",
								"personality": ["Playful", "Creative", "Confident"],
								"interests": ["Art", "Gaming", "Music"]
							}
						}
					}
				},
				"responses": {
					"201": {
						"description": "Digital twin creation started. Poll status endpoint for completion.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"success": {
											"type": "boolean",
											"example": true
										},
										"digitalTwinId": {
											"type": "string",
											"format": "uuid",
											"description": "Digital twin UUID for status polling"
										},
										"status": {
											"$ref": "#/components/schemas/DigitalTwinStatus"
										},
										"message": {
											"type": "string",
											"example": "Digital twin creation started. Poll GET /api/v1/digital-twins/status/{digitalTwinId} for progress."
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "Validation error - missing required fields or character under 21"
					},
					"401": {
						"description": "Unauthorized - invalid or missing API key"
					},
					"500": {
						"description": "Server error - image download failed or database error"
					}
				}
			}
		},
		"/api/v1/digital-twins/{ohChatId}": {
			"patch": {
				"tags": ["Digital Twins"],
				"summary": "Update Digital Twin",
				"description": "Updates an existing digital twin character. All fields are optional for partial updates. Note: reference image and ownership cannot be changed after creation.",
				"operationId": "updateDigitalTwin",
				"parameters": [
					{
						"name": "ohChatId",
						"in": "path",
						"required": true,
						"description": "OhChat character ID (from the characterId field in status response)",
						"schema": {
							"type": "integer"
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/UpdateDigitalTwinRequest"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Digital twin updated successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"success": {
											"type": "boolean",
											"example": true
										},
										"digitalTwinId": {
											"type": "string",
											"format": "uuid"
										},
										"characterId": {
											"type": "integer"
										},
										"updatedFields": {
											"type": "array",
											"items": {
												"type": "string"
											},
											"example": ["bio", "hairColour", "interests"]
										},
										"message": {
											"type": "string",
											"example": "Digital twin updated successfully. 3 field(s) modified."
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "Invalid data - age under 21 or invalid values"
					},
					"401": {
						"description": "Unauthorized - invalid or missing API key"
					},
					"404": {
						"description": "Digital twin not found"
					},
					"500": {
						"description": "Server error - image download failed or database error"
					}
				}
			}
		},
		"/api/v1/digital-twins/status/{digitalTwinId}": {
			"get": {
				"tags": ["Digital Twins"],
				"summary": "Get Digital Twin Status",
				"description": "Returns the current processing status of a digital twin. Poll this endpoint after create/update operations to track progress. When status is 'active', the characterId field is populated and can be used for subsequent update calls.",
				"operationId": "getDigitalTwinStatus",
				"parameters": [
					{
						"name": "digitalTwinId",
						"in": "path",
						"required": true,
						"description": "Digital Twin UUID returned from the create endpoint",
						"schema": {
							"type": "string",
							"format": "uuid"
						}
					}
				],
				"responses": {
					"200": {
						"description": "Digital twin status retrieved",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"digitalTwinId": {
											"type": "string",
											"format": "uuid"
										},
										"status": {
											"$ref": "#/components/schemas/DigitalTwinStatus"
										},
										"characterId": {
											"type": "integer",
											"description": "Populated when status is 'active' - use this for update calls"
										},
										"errorMessage": {
											"type": "string",
											"nullable": true,
											"description": "Error message if processing failed"
										},
										"createdAt": {
											"type": "string",
											"format": "date-time"
										},
										"updatedAt": {
											"type": "string",
											"format": "date-time"
										}
									}
								},
								"examples": {
									"pending": {
										"summary": "Still processing",
										"value": {
											"digitalTwinId": "550e8400-e29b-41d4-a716-446655440000",
											"status": "pending",
											"characterId": null,
											"errorMessage": null
										}
									},
									"active": {
										"summary": "Ready to use",
										"value": {
											"digitalTwinId": "550e8400-e29b-41d4-a716-446655440000",
											"status": "active",
											"characterId": 12345,
											"errorMessage": null
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "Digital twin not found"
					}
				}
			}
		},
		"/api/v1/videos/get": {
			"get": {
				"tags": ["Videos"],
				"summary": "Check Video Status (Image-to-Video)",
				"description": "Poll this endpoint to check the status of an image-to-video generation job. Use the `id` returned from the video creation response. Recommended polling interval: every 5 seconds, with a maximum timeout of 5 minutes. Once status is `READY`, the `video_url` field contains a presigned download URL.",
				"operationId": "getVideoStatus",
				"x-code-samples": [
					{
						"lang": "JavaScript",
						"source": "async function pollVideo(videoId, apiKey, maxAttempts = 60) {\n  for (let i = 0; i < maxAttempts; i++) {\n    const response = await fetch(\n      `https://api.oh.xyz/api/v1/videos/get?videoId=${videoId}`,\n      { headers: { 'x-api-key': apiKey } }\n    );\n    const data = await response.json();\n\n    if (data.status === 'READY') {\n      console.log('Video URL:', data.video_url);\n      return data;\n    }\n    if (data.status === 'FAILED') {\n      throw new Error('Video generation failed');\n    }\n\n    await new Promise(r => setTimeout(r, 5000));\n  }\n  throw new Error('Polling timed out');\n}"
					},
					{
						"lang": "cURL",
						"source": "curl -X GET 'https://api.oh.xyz/api/v1/videos/get?videoId=YOUR_VIDEO_ID' \\\n  -H 'x-api-key: YOUR_API_KEY'"
					},
					{
						"lang": "Python",
						"source": "import requests, time\n\ndef poll_video(video_id, api_key, max_attempts=60):\n    for _ in range(max_attempts):\n        data = requests.get(\n            f'https://api.oh.xyz/api/v1/videos/get?videoId={video_id}',\n            headers={'x-api-key': api_key}\n        ).json()\n\n        if data['status'] == 'READY':\n            print('Video URL:', data['video_url'])\n            return data\n        if data['status'] == 'FAILED':\n            raise Exception('Video generation failed')\n\n        time.sleep(5)\n    raise TimeoutError('Polling timed out')"
					}
				],
				"parameters": [
					{
						"name": "videoId",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string"
						},
						"description": "The video ID returned from the image-to-video creation request"
					}
				],
				"responses": {
					"200": {
						"description": "Video status retrieved",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Video job ID"
										},
										"status": {
											"type": "string",
											"enum": ["GENERATING", "READY", "FAILED"],
											"description": "Current job status"
										},
										"video_url": {
											"type": "string",
											"nullable": true,
											"description": "Presigned download URL. Only present when status is READY."
										}
									}
								},
								"examples": {
									"generating": {
										"summary": "Video is still generating",
										"value": {
											"id": "abc123-def456",
											"status": "GENERATING"
										}
									},
									"ready": {
										"summary": "Video is ready",
										"value": {
											"id": "abc123-def456",
											"status": "READY",
											"video_url": "https://s3.amazonaws.com/bucket/videos/abc123/output.mp4?X-Amz-..."
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "Video not found"
					}
				}
			}
		},
		"/api/v1/videos/delete": {
			"delete": {
				"tags": ["Videos"],
				"summary": "Delete Video",
				"description": "Delete a video by ID.",
				"operationId": "deleteVideo",
				"parameters": [
					{
						"name": "videoId",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string"
						},
						"description": "The video ID to delete"
					}
				],
				"responses": {
					"200": {
						"description": "Video deleted successfully",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"ok": {
											"type": "boolean"
										},
										"status": {
											"type": "string"
										}
									}
								},
								"example": {
									"ok": true,
									"status": "deleted"
								}
							}
						}
					},
					"404": {
						"description": "Video not found"
					}
				}
			}
		}
	},
	"components": {
		"securitySchemes": {
			"bearerAuth": {
				"type": "http",
				"scheme": "bearer",
				"bearerFormat": "API Key",
				"description": "Use your API key as a Bearer token"
			},
			"X-Internal-API-Key": {
				"type": "apiKey",
				"in": "header",
				"name": "X-Internal-API-Key"
			}
		},
		"schemas": {
			"CreateCharacterRequest": {
				"type": "object",
				"properties": {}
			},
			"CharacterStatus": {
				"type": "string",
				"enum": ["draft", "generating", "ready", "saving", "saved", "failed"],
				"description": "V2 character processing status:\n- **draft**: Initial state\n- **generating**: AI generation in progress\n- **ready**: Generation complete, ready to save\n- **saving**: Save in progress\n- **saved**: Fully saved and ready to use\n- **failed**: Processing failed (check errorMessage)"
			},
			"GenerateCharacterRequest": {
				"type": "object",
				"required": [
					"nationality",
					"ethnicity",
					"firstName",
					"lastName",
					"biography",
					"gender"
				],
				"properties": {
					"nationality": {
						"type": "string",
						"description": "Character nationality. Use GET /api/v1/characters/nationalities for valid values",
						"example": "American"
					},
					"ethnicity": {
						"type": "string",
						"description": "Character ethnicity. Use GET /api/v1/characters/ethnicities for valid values",
						"example": "Caucasian"
					},
					"firstName": {
						"type": "string",
						"description": "Character first name",
						"example": "Aria"
					},
					"lastName": {
						"type": "string",
						"description": "Character last name",
						"example": "Storm"
					},
					"biography": {
						"type": "string",
						"description": "Character biography/bio text. Auto-generated if missing",
						"example": "A free-spirited artist with a passion for adventure"
					},
					"dateOfBirth": {
						"type": "string",
						"format": "date",
						"description": "Date of birth (ISO format). Must be 21+. Defaults to age 27 if not provided",
						"example": "1995-06-15"
					},
					"alias": {
						"type": "string",
						"description": "Character alias/nickname"
					},
					"job": {
						"type": "string",
						"description": "Character occupation"
					},
					"whereYouLive": {
						"type": "string",
						"description": "Location where character lives"
					},
					"gender": {
						"type": "string",
						"enum": ["Female", "Male"],
						"description": "Character gender"
					},
					"penisSize": {
						"type": "string",
						"description": "Penis size (male only)",
						"enum": ["Small", "Huge", "Enormous"]
					},
					"vaginaHair": {
						"type": "string",
						"description": "Pubic hair style (female only). Use GET /api/v1/characters/vagina-hair for valid values",
						"enum": [
							"Bald",
							"Designer",
							"Diamond shape",
							"French wax",
							"Full bush",
							"Heart shape",
							"Landing strip",
							"Lightning bolt",
							"Long",
							"Natural",
							"Neat triangle",
							"Partially shaved sides",
							"Shaved",
							"Small trimmed patch",
							"Smooth wax",
							"Thin landing strip",
							"Thin vertical strip",
							"Tiny Brazilian",
							"Trimmed",
							"Trimmed short",
							"V shaped",
							"Wild"
						]
					},
					"vaginaSize": {
						"type": "string",
						"description": "Vagina size (female only). Use GET /api/v1/characters/vagina-sizes for valid values",
						"enum": [
							"Accommodating",
							"Average",
							"Extremely tight",
							"Gilf",
							"Gripping",
							"Incredibly tight",
							"Loose & wet",
							"Milf",
							"Normal",
							"Perfectly snug",
							"Petite tight",
							"Post-baby looser",
							"Relaxed",
							"Slightly looser",
							"Snug",
							"Super tight",
							"Tight",
							"Very Accommodating",
							"Very tight",
							"Well-used",
							"Youthfully tight"
						]
					},
					"physicalCharacteristics": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Array of additional physical features not covered by other fields (e.g., glasses, freckles, pregnancy, piercings)",
						"example": ["wears glasses", "has freckles", "beauty mark on cheek"]
					},
					"orientation": {
						"type": "string",
						"description": "Character's sexual orientation. Affects who the character is attracted to in text conversations.",
						"enum": [
							"90/10",
							"Asexual",
							"Bi-curious",
							"Bicurious",
							"Bisexual",
							"Demisexual",
							"Experimenting",
							"Fluid",
							"Gay",
							"Heteroflexible",
							"Homoflexible",
							"Lesbian",
							"Mostly straight",
							"Open to anything",
							"Pansexual",
							"Queer",
							"Sapiosexual",
							"Straight",
							"Straight but plays with girls"
						]
					},
					"personality": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Character's personality archetype. Shapes the character's overall behavior and tone in text conversations.",
						"example": ["Adventurous", "Playful", "Confident"]
					},
					"interests": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Character interests. Values must match valid terms — use GET /api/v1/characters/interests for the full list. Auto-generated if missing",
						"example": ["Photography", "Travel", "Cooking"]
					},
					"kinks": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Character's sexual preferences and kinks. Used to shape the character's personality and behavior in text conversations only — they do not control image or video generation capabilities."
					},
					"hairLength": {
						"type": "string",
						"description": "Hair length. Use GET /api/v1/characters/hair-lengths for valid values",
						"example": "Long"
					},
					"hairColour": {
						"type": "string",
						"description": "Hair colour. Use GET /api/v1/characters/hair-colours for valid values",
						"example": "Blonde"
					},
					"eyeColour": {
						"type": "string",
						"description": "Eye colour. Use GET /api/v1/characters/eye-colours for valid values",
						"example": "Blue"
					},
					"breastSize": {
						"type": "string",
						"description": "Breast size (female only). Use GET /api/v1/characters/breast-sizes for valid values. Auto-generated if missing",
						"example": "Medium"
					},
					"bodyBuild": {
						"type": "string",
						"description": "Body build type. Use GET /api/v1/characters/body-builds for valid values",
						"example": "Athletic"
					},
					"backstory": {
						"type": "string",
						"description": "Character backstory. Auto-generated if missing"
					},
					"textingStyle": {
						"type": "string",
						"description": "texting/communication style description. Auto-generated if missing",
						"example": "Playful and flirty with lots of emojis"
					},
					"tattoos": {
						"type": "string",
						"description": "tattoo description. Auto-generated if missing",
						"example": "Small butterfly on right shoulder"
					},
					"profileImageUrl": {
						"type": "string",
						"format": "uri",
						"description": "URL to profile image (downloaded and saved automatically)"
					},
					"coverVideoUrl": {
						"type": "string",
						"format": "uri",
						"description": "URL to cover video (downloaded and saved automatically)"
					},
					"voiceUrl": {
						"type": "string",
						"format": "uri",
						"description": "HTTP URL to voice audio file (downloaded automatically)"
					},
					"audioSpeed": {
						"type": "number",
						"format": "float",
						"minimum": 0.5,
						"maximum": 2.0,
						"default": 0.5,
						"description": "Audio playback speed multiplier"
					},
					"socialHandleInstagram": {
						"type": "string",
						"description": "Instagram handle"
					},
					"socialHandleTiktok": {
						"type": "string",
						"description": "TikTok handle"
					},
					"socialHandleTwitter": {
						"type": "string",
						"description": "X/Twitter handle"
					},
					"height": {
						"type": "string",
						"description": "Character height",
						"example": "5'7\""
					},
					"bodyShape": {
						"type": "string",
						"description": "Body shape. Use GET /api/v1/characters/body-shapes for valid values",
						"enum": [
							"Apple",
							"Athletic",
							"Balanced",
							"Bottom-heavy pear",
							"Busty rectangle",
							"Chubby",
							"Hourglass",
							"Inverted triangle",
							"Pear",
							"Petite hourglass",
							"Rectangle",
							"Slim hourglass",
							"Soft pear",
							"Thick hourglass",
							"Top-heavy",
							"Voluptuous hourglass"
						]
					},
					"buttSize": {
						"type": "string",
						"description": "Butt size. Use GET /api/v1/characters/butt-sizes for valid values",
						"enum": [
							"Average",
							"Big",
							"Bubble Butt",
							"Firm & round",
							"Flat",
							"Heart-shaped",
							"Huge",
							"Jiggly",
							"Juicy",
							"Massive",
							"Peach",
							"Perky",
							"Phat",
							"Round & Full",
							"Shelf",
							"Small",
							"Thick",
							"Wide"
						]
					},
					"skinTone": {
						"type": "string",
						"description": "Skin tone. Use GET /api/v1/characters/skin-tones for valid values",
						"enum": [
							"Alabaster",
							"Beige",
							"Caramel",
							"Deep Brown",
							"Deep Tan",
							"Ebony",
							"Espresso",
							"Golden",
							"Golden Olive",
							"Honey",
							"Latte",
							"Light",
							"Light Caramel",
							"Light Olive",
							"Mahogany",
							"Medium Brown",
							"Mocha",
							"Olive",
							"Pale Ivory",
							"Rich Brown",
							"Sun-Kissed",
							"Tan",
							"Tawny",
							"Very Pale",
							"Warm Honey",
							"Warm Ivory"
						]
					},
					"breastPertness": {
						"type": "string",
						"description": "Breast pertness (female only). Use GET /api/v1/characters/breast-pertness for valid values",
						"example": "Perky"
					},
					"nippleColour": {
						"type": "string",
						"description": "Nipple colour (female only). Use GET /api/v1/characters/nipple-colours for valid values",
						"enum": [
							"Almost black",
							"Caramel brown",
							"Dark brown",
							"Dark pink",
							"Deep brown",
							"Dusky rose",
							"Light brown",
							"Light pink",
							"Mauve",
							"Medium brown",
							"Medium pink",
							"Peachy pink",
							"Pink",
							"Puffy dark pink",
							"Puffy light pink",
							"Reddish-pink",
							"Rosy brown",
							"Tan brown"
						]
					},
					"relationshipStatus": {
						"type": "string",
						"description": "Relationship status"
					},
					"imageModel": {
						"type": "string",
						"description": "Image model for generation",
						"default": "flux"
					},
					"contentLevel": {
						"type": "string",
						"description": "Maximum content level the character will engage with. Determines what the character is willing to do in both text conversations and media generation. Sexy = suggestive/clothed, Topless = partial nudity, Nudes = full nudity, Sex = explicit sexual content.",
						"enum": ["Sexy", "Topless", "Nudes", "Sex"],
						"example": "Sex"
					},
					"bodyType": {
						"type": "string",
						"description": "Body type (valid value from lookup endpoint)"
					},
					"eyeColor": {
						"type": "string",
						"description": "Eye color (valid value from lookup endpoint)"
					},
					"hairColor": {
						"type": "string",
						"description": "Hair color (valid value from lookup endpoint)"
					},
					"socialHandleYoutube": {
						"type": "string",
						"description": "YouTube handle"
					},
					"socialHandleTwitch": {
						"type": "string",
						"description": "Twitch handle"
					},
					"socialHandleTelegram": {
						"type": "string",
						"description": "Telegram handle"
					},
					"earlyAccessEmail": {
						"type": "integer",
						"description": "Early access email entity reference"
					},
					"weight": {
						"type": "integer",
						"description": "Character weight/ordering",
						"default": 200
					},
					"priorityPrice": {
						"type": "number",
						"format": "float",
						"default": 9.99,
						"description": "Priority subscription price (field_sub_a)"
					},
					"platinumPrice": {
						"type": "number",
						"format": "float",
						"default": 19.99,
						"description": "Platinum subscription price (field_sub_b)"
					},
					"vipPrice": {
						"type": "number",
						"format": "float",
						"default": 39.99,
						"description": "VIP subscription price (field_sub_c)"
					},
					"priorityListPrice": {
						"type": "number",
						"format": "float",
						"default": 19.99,
						"description": "Priority list price (field_list_price_a)"
					},
					"sexDrive": {
						"type": "string",
						"description": "Character's sex drive level. Affects the character's eagerness and initiative in text conversations.",
						"enum": [
							"Always horny",
							"Asexual but enjoys pleasing",
							"Average",
							"High",
							"High but shy",
							"Hypersexual nympho",
							"Insatiable",
							"Low",
							"Low until aroused then wild",
							"Moderate",
							"Morning sex addict",
							"Needs it to fall asleep",
							"Night owl horny",
							"Once a day minimum",
							"Only horny when ovulating",
							"Permanently in heat",
							"Switches normal to crazy",
							"Touch-starved",
							"Very high",
							"Very low cuddly"
						]
					},
					"conversationStyle": {
						"type": "string",
						"description": "Character's conversation style. Controls the tone and manner of the character's text responses.",
						"enum": [
							"Baby-talk / little space",
							"Bratty & sassy",
							"Bubbly & giggly",
							"Constant compliments & praise",
							"Country / southern drawl",
							"Dirty talk queen",
							"Dominant & commanding",
							"Flirty & teasing",
							"Foul-mouthed & trashy",
							"High-maintenance princess",
							"Low-key & chill",
							"Nerdy & rambling",
							"Proper & polite",
							"Sarcastic & witty",
							"Shy & soft-spoken",
							"Submissive & obedient",
							"Sweet & innocent",
							"Tsundere",
							"Valley girl",
							"Weeb / anime speech"
						]
					},
					"sexualExperience": {
						"type": "string",
						"description": "Character's sexual experience level. Affects the character's personality and how they discuss sexual topics in text conversations.",
						"enum": [
							"100+ partners",
							"Almost virgin",
							"Always slutty",
							"Experienced",
							"Former escort",
							"Highly experienced",
							"Innocent but curious",
							"Lots of threesomes",
							"Moderate",
							"Mostly one-night stands",
							"Nymphomaniac",
							"Only relationships",
							"Porn-level",
							"Recently awakened",
							"Reformed good girl",
							"Sheltered then wild",
							"Swinger",
							"Very experienced",
							"Very limited",
							"Virgin"
						]
					},
					"additionalDescription": {
						"type": "string",
						"maxLength": 1000,
						"description": "Free-text traits added to the reference image that the structured fields can't express. Examples: `nose piercing`, `hooped earrings`, `cherry blossom sleeve tattoo`, `wire-frame glasses`, `thin scar above the left eyebrow`.",
						"example": "six months pregnant, small dragon tattoo across the right hip, wire-frame glasses, a thin scar above the left eyebrow"
					}
				}
			},
			"SaveCharacterRequest": {
				"type": "object",
				"required": ["characterGuid"],
				"properties": {
					"characterGuid": {
						"type": "string",
						"format": "uuid",
						"description": "Character GUID from the generate step"
					},
					"firstName": {
						"type": "string",
						"description": "Override first name"
					},
					"lastName": {
						"type": "string",
						"description": "Override last name"
					},
					"biography": {
						"type": "string",
						"description": "Override biography"
					},
					"dateOfBirth": {
						"type": "string",
						"format": "date",
						"description": "Override date of birth (must be 21+)"
					},
					"personality": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Override personality traits. Values must match valid terms — use GET /api/v1/characters/personalities for the full list"
					},
					"interests": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Override interests. Values must match valid terms — use GET /api/v1/characters/interests for the full list"
					},
					"kinks": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Character's sexual preferences and kinks. Used to shape the character's personality and behavior in text conversations only — they do not control image or video generation capabilities."
					},
					"backstory": {
						"type": "string",
						"description": "Override backstory"
					},
					"tattoos": {
						"type": "string",
						"description": "Override tattoo description",
						"example": "Small butterfly on right shoulder"
					},
					"textingStyle": {
						"type": "string",
						"description": "Override texting/communication style",
						"example": "Playful and flirty with lots of emojis"
					},
					"physicalCharacteristics": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Override physical characteristics (array of strings)",
						"example": ["wears glasses", "has freckles"]
					},
					"penisSize": {
						"type": "string",
						"description": "Override penis size (male only)"
					},
					"breastSize": {
						"type": "string",
						"description": "Override breast size (female only)"
					},
					"ethnicity": {
						"type": "string",
						"description": "Override ethnicity"
					},
					"hairLength": {
						"type": "string",
						"description": "Override hair length"
					},
					"voiceUrl": {
						"type": "string",
						"format": "uri",
						"description": "HTTP URL to voice reference audio file (.wav). Overrides any voice set at generate time"
					},
					"alias": {
						"type": "string",
						"description": "Override alias/nickname"
					},
					"job": {
						"type": "string",
						"description": "Override job title"
					},
					"whereYouLive": {
						"type": "string",
						"description": "Override location"
					},
					"gender": {
						"type": "string",
						"description": "Override gender"
					},
					"orientation": {
						"type": "string",
						"description": "Override sexual orientation"
					},
					"relationshipStatus": {
						"type": "string",
						"description": "Override relationship status"
					},
					"profileImageUrl": {
						"type": "string",
						"format": "uri",
						"description": "URL to profile image (downloaded, cropped to 1:1, saved to S3)"
					},
					"coverVideoUrl": {
						"type": "string",
						"format": "uri",
						"description": "URL to cover video (downloaded and saved to S3)"
					},
					"imageModel": {
						"type": "string",
						"description": "Image model for generation",
						"default": "flux"
					},
					"audioSpeed": {
						"type": "number",
						"format": "float",
						"minimum": 0.5,
						"maximum": 2.0,
						"default": 0.5,
						"description": "Audio playback speed multiplier"
					},
					"socialHandleInstagram": {
						"type": "string",
						"description": "Instagram handle"
					},
					"socialHandleTiktok": {
						"type": "string",
						"description": "TikTok handle"
					},
					"socialHandleTwitter": {
						"type": "string",
						"description": "X/Twitter handle"
					},
					"socialHandleYoutube": {
						"type": "string",
						"description": "YouTube handle"
					},
					"socialHandleTwitch": {
						"type": "string",
						"description": "Twitch handle"
					},
					"socialHandleTelegram": {
						"type": "string",
						"description": "Telegram handle"
					},
					"contentLevel": {
						"$ref": "#/components/schemas/GenerateCharacterRequest/properties/contentLevel",
						"description": "Maximum content level the character will engage with. Determines what the character is willing to do in both text conversations and media generation. Sexy = suggestive/clothed, Topless = partial nudity, Nudes = full nudity, Sex = explicit sexual content."
					},
					"bodyType": {
						"type": "string",
						"description": "Override body type (valid value from lookup endpoint)"
					},
					"eyeColor": {
						"type": "string",
						"description": "Override eye color (valid value from lookup endpoint)"
					},
					"hairColor": {
						"type": "string",
						"description": "Override hair color (valid value from lookup endpoint)"
					},
					"earlyAccessEmail": {
						"type": "integer",
						"description": "Early access email entity reference"
					},
					"weight": {
						"type": "integer",
						"description": "Character weight/ordering",
						"default": 200
					},
					"uid": {
						"type": "integer",
						"description": "User ID in OhChat system"
					},
					"priorityPrice": {
						"type": "number",
						"format": "float",
						"default": 9.99,
						"description": "Override priority subscription price (field_sub_a)"
					},
					"platinumPrice": {
						"type": "number",
						"format": "float",
						"default": 19.99,
						"description": "Override platinum subscription price (field_sub_b)"
					},
					"vipPrice": {
						"type": "number",
						"format": "float",
						"default": 39.99,
						"description": "Override vIP subscription price (field_sub_c)"
					},
					"priorityListPrice": {
						"type": "number",
						"format": "float",
						"default": 19.99,
						"description": "Override priority list price (field_list_price_a)"
					},
					"platinumListPrice": {
						"type": "number",
						"format": "float",
						"default": 29.99,
						"description": "Override platinum list price (field_list_price_b)"
					}
				}
			},
			"DigitalTwinStatus": {
				"type": "string",
				"enum": ["pending", "active", "draft", "inactive"],
				"description": "Digital twin processing status:\n- **pending**: Creation/update in progress\n- **active**: Digital twin is ready and live\n- **draft**: Initial state before processing\n- **inactive**: Deactivated"
			},
			"CreateDigitalTwinRequest": {
				"type": "object",
				"required": [
					"name",
					"alias",
					"dateOfBirth",
					"job",
					"location",
					"gender",
					"orientation",
					"hairColour",
					"eyeColour",
					"bodyType",
					"referenceImageUrl"
				],
				"properties": {
					"name": {
						"type": "string",
						"description": "Character display name",
						"example": "Luna Starfire"
					},
					"alias": {
						"type": "string",
						"description": "Character nickname / short name",
						"example": "Luna"
					},
					"dateOfBirth": {
						"type": "string",
						"format": "date",
						"description": "Date of birth (ISO format). Character must be 21+",
						"example": "1998-06-15"
					},
					"job": {
						"type": "string",
						"description": "Character occupation",
						"example": "Digital Artist"
					},
					"location": {
						"type": "string",
						"description": "Where the character lives",
						"example": "Los Angeles, CA"
					},
					"gender": {
						"$ref": "#/components/schemas/GenerateCharacterRequest/properties/gender"
					},
					"orientation": {
						"$ref": "#/components/schemas/GenerateCharacterRequest/properties/orientation"
					},
					"hairColour": {
						"type": "string",
						"description": "Hair colour. Use GET /api/v1/characters/hair-colours for valid values",
						"example": "Blonde"
					},
					"eyeColour": {
						"type": "string",
						"description": "Eye colour. Use GET /api/v1/characters/eye-colours for valid values",
						"example": "Blue"
					},
					"bodyType": {
						"type": "string",
						"description": "Body type. Use GET /api/v1/characters/body-builds for valid values",
						"example": "Athletic"
					},
					"referenceImageUrl": {
						"type": "string",
						"format": "uri",
						"description": "URL to reference image (downloaded and saved to S3)",
						"example": "https://cdn.example.com/images/reference.jpg"
					},
					"contentLevel": {
						"$ref": "#/components/schemas/GenerateCharacterRequest/properties/contentLevel"
					},
					"profileImageUrl": {
						"type": "string",
						"format": "uri",
						"description": "URL to profile image (converted to PNG and saved)",
						"example": "https://cdn.example.com/images/profile.jpg"
					},
					"bio": {
						"type": "string",
						"description": "Character biography"
					},
					"textingStyle": {
						"type": "string",
						"description": "Texting/communication style",
						"example": "Playful and flirty with lots of emojis"
					},
					"ethnicity": {
						"type": "string",
						"description": "Character ethnicity. Use GET /api/v1/characters/ethnicities for valid values",
						"example": "Caucasian"
					},
					"hairLength": {
						"type": "string",
						"description": "Hair length. Use GET /api/v1/characters/hair-lengths for valid values",
						"example": "Long"
					},
					"breastSize": {
						"type": "string",
						"description": "Breast size (female only). Use GET /api/v1/characters/breast-sizes for valid values",
						"example": "Medium"
					},
					"tattoos": {
						"type": "string",
						"description": "Tattoo description",
						"example": "Small butterfly on shoulder"
					},
					"physicalCharacteristics": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Physical characteristics (array of strings)",
						"example": ["Freckles", "Dimples", "Beauty mark"]
					},
					"personality": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Personality traits. Values must match valid terms — use GET /api/v1/characters/personalities for the full list",
						"example": ["Playful", "Creative", "Confident"]
					},
					"interests": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Character interests. Values must match valid terms — use GET /api/v1/characters/interests for the full list",
						"example": ["Art", "Gaming", "Music"]
					},
					"kinks": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Character kinks. Values must match valid terms — use GET /api/v1/characters/kinks for the full list"
					},
					"audioSpeed": {
						"type": "number",
						"format": "float",
						"minimum": 0.5,
						"maximum": 2.0,
						"default": 1.0,
						"description": "Audio playback speed multiplier"
					},
					"socialHandleInstagram": {
						"type": "string",
						"description": "Instagram handle",
						"example": "@luna_starfire"
					},
					"vaginaHair": {
						"type": "string",
						"description": "Pubic hair style (female only). Use GET /api/v1/characters/vagina-hair for valid values",
						"example": "Landing strip"
					},
					"relationshipStatus": {
						"type": "string",
						"description": "Relationship status"
					},
					"backstory": {
						"type": "string",
						"description": "Character backstory. Auto-generated if not provided"
					},
					"coverVideoUrl": {
						"type": "string",
						"format": "uri",
						"description": "URL to cover video (downloaded and saved to S3)"
					},
					"voiceUrl": {
						"type": "string",
						"format": "uri",
						"description": "HTTP URL to voice reference audio file (.wav). For best results, ~30 seconds of clear speech",
						"example": "https://example.com/voice.wav"
					},
					"socialHandleTiktok": {
						"type": "string",
						"description": "TikTok handle"
					},
					"socialHandleTwitter": {
						"type": "string",
						"description": "X/Twitter handle"
					},
					"socialHandleYoutube": {
						"type": "string",
						"description": "YouTube handle"
					},
					"socialHandleTwitch": {
						"type": "string",
						"description": "Twitch handle"
					},
					"socialHandleTelegram": {
						"type": "string",
						"description": "Telegram handle"
					},
					"earlyAccessEmail": {
						"type": "integer",
						"description": "Early access email entity reference"
					},
					"priorityPrice": {
						"type": "number",
						"format": "float",
						"default": 9.99,
						"description": "Priority subscription price (field_sub_a)"
					},
					"platinumPrice": {
						"type": "number",
						"format": "float",
						"default": 19.99,
						"description": "Platinum subscription price (field_sub_b)"
					},
					"vipPrice": {
						"type": "number",
						"format": "float",
						"default": 39.99,
						"description": "VIP subscription price (field_sub_c)"
					},
					"priorityListPrice": {
						"type": "number",
						"format": "float",
						"default": 19.99,
						"description": "Priority list price (field_list_price_a)"
					},
					"platinumListPrice": {
						"type": "number",
						"format": "float",
						"default": 29.99,
						"description": "Platinum list price (field_list_price_b)"
					}
				}
			},
			"UpdateDigitalTwinRequest": {
				"type": "object",
				"description": "All fields are optional for partial updates. Reference image and ownership cannot be changed after creation.",
				"properties": {
					"name": {
						"type": "string",
						"description": "Character display name"
					},
					"alias": {
						"type": "string",
						"description": "Character nickname"
					},
					"dateOfBirth": {
						"type": "string",
						"format": "date",
						"description": "Date of birth (must remain 21+)"
					},
					"job": {
						"type": "string",
						"description": "Character occupation"
					},
					"location": {
						"type": "string",
						"description": "Where the character lives"
					},
					"bio": {
						"type": "string",
						"description": "Character biography"
					},
					"textingStyle": {
						"type": "string",
						"description": "Texting style"
					},
					"personality": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Update personality traits. Values must match valid terms — use GET /api/v1/characters/personalities for the full list"
					},
					"interests": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Update interests. Values must match valid terms — use GET /api/v1/characters/interests for the full list"
					},
					"profileImageUrl": {
						"type": "string",
						"format": "uri",
						"description": "New profile image URL (downloaded and saved)"
					},
					"gender": {
						"$ref": "#/components/schemas/GenerateCharacterRequest/properties/gender"
					},
					"orientation": {
						"$ref": "#/components/schemas/GenerateCharacterRequest/properties/orientation"
					},
					"hairColour": {
						"type": "string",
						"description": "Update hair colour. Use GET /api/v1/characters/hair-colours for valid values"
					},
					"eyeColour": {
						"type": "string",
						"description": "Update eye colour. Use GET /api/v1/characters/eye-colours for valid values"
					},
					"bodyType": {
						"type": "string",
						"description": "Update body type. Use GET /api/v1/characters/body-builds for valid values"
					},
					"ethnicity": {
						"type": "string",
						"description": "Update ethnicity. Use GET /api/v1/characters/ethnicities for valid values"
					},
					"hairLength": {
						"type": "string",
						"description": "Update hair length. Use GET /api/v1/characters/hair-lengths for valid values"
					},
					"breastSize": {
						"type": "string",
						"description": "Update breast size (female only). Use GET /api/v1/characters/breast-sizes for valid values"
					},
					"vaginaHair": {
						"type": "string",
						"description": "Update pubic hair style (female only). Use GET /api/v1/characters/vagina-hair for valid values"
					},
					"contentLevel": {
						"$ref": "#/components/schemas/GenerateCharacterRequest/properties/contentLevel"
					},
					"tattoos": {
						"type": "string",
						"description": "Update tattoo description"
					},
					"physicalCharacteristics": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Update physical characteristics (array of strings)"
					},
					"kinks": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "Update kinks. Values must match valid terms — use GET /api/v1/characters/kinks for the full list"
					},
					"relationshipStatus": {
						"type": "string",
						"description": "Update relationship status"
					},
					"backstory": {
						"type": "string",
						"description": "Update character backstory"
					},
					"referenceImageUrl": {
						"type": "string",
						"format": "uri",
						"description": "URL to new reference image"
					},
					"coverVideoUrl": {
						"type": "string",
						"format": "uri",
						"description": "URL to cover video"
					},
					"voiceUrl": {
						"type": "string",
						"format": "uri",
						"description": "HTTP URL to voice reference audio file (.wav)"
					},
					"audioSpeed": {
						"type": "number",
						"format": "float",
						"minimum": 0.5,
						"maximum": 2.0,
						"description": "Audio playback speed multiplier"
					},
					"socialHandleInstagram": {
						"type": "string",
						"description": "Instagram handle"
					},
					"socialHandleTiktok": {
						"type": "string",
						"description": "TikTok handle"
					},
					"socialHandleTwitter": {
						"type": "string",
						"description": "X/Twitter handle"
					},
					"socialHandleYoutube": {
						"type": "string",
						"description": "YouTube handle"
					},
					"socialHandleTwitch": {
						"type": "string",
						"description": "Twitch handle"
					},
					"socialHandleTelegram": {
						"type": "string",
						"description": "Telegram handle"
					},
					"earlyAccessEmail": {
						"type": "integer",
						"description": "Early access email entity reference"
					},
					"priorityPrice": {
						"type": "number",
						"format": "float",
						"default": 9.99,
						"description": "Update priority subscription price (field_sub_a)"
					},
					"platinumPrice": {
						"type": "number",
						"format": "float",
						"default": 19.99,
						"description": "Update platinum subscription price (field_sub_b)"
					},
					"vipPrice": {
						"type": "number",
						"format": "float",
						"default": 39.99,
						"description": "Update vIP subscription price (field_sub_c)"
					},
					"priorityListPrice": {
						"type": "number",
						"format": "float",
						"default": 19.99,
						"description": "Update priority list price (field_list_price_a)"
					},
					"platinumListPrice": {
						"type": "number",
						"format": "float",
						"default": 29.99,
						"description": "Update platinum list price (field_list_price_b)"
					}
				}
			},
			"GenerateAiCharacterRequest": {
				"type": "object",
				"required": [
					"nationality",
					"ethnicity",
					"firstName",
					"lastName",
					"biography"
				],
				"properties": {
					"b2bClientId": {
						"type": "string",
						"description": "B2B client ID (required if ohFunCreatorId not provided)"
					},
					"ohFunCreatorId": {
						"type": "string",
						"description": "OhFun creator ID (required if b2bClientId not provided)"
					},
					"nationality": {
						"type": "string",
						"description": "Character nationality",
						"enum": [
							"American",
							"Brazilian",
							"Russian",
							"Colombian",
							"Ukrainian",
							"Japanese",
							"Filipino",
							"Thai",
							"Mexican",
							"Canadian",
							"British",
							"Korean (South)",
							"Vietnamese",
							"Indian",
							"Chinese",
							"German",
							"French",
							"Italian",
							"Spanish",
							"Australian",
							"Swedish",
							"Polish",
							"Dutch",
							"Venezuelan",
							"Dominican",
							"Argentine",
							"Peruvian",
							"Czech",
							"Romanian",
							"Hungarian",
							"Turkish",
							"Lebanese",
							"Israeli",
							"Greek",
							"Serbian",
							"Croatian",
							"Bulgarian",
							"Belgian",
							"Norwegian",
							"Danish",
							"Finnish",
							"Irish",
							"Scottish",
							"Portuguese",
							"New Zealander",
							"South African",
							"Jamaican",
							"Puerto Rican",
							"Cuban",
							"Costa Rican",
							"Panamanian",
							"Salvadoran",
							"Nigerian",
							"Ghanaian",
							"Kenyan",
							"Moroccan",
							"Algerian",
							"Egyptian",
							"Tunisian",
							"Pakistani",
							"Bangladeshi",
							"Indonesian",
							"Malaysian",
							"Singaporean",
							"Taiwanese",
							"Hong Konger",
							"Emirati (UAE)",
							"Saudi",
							"Qatari",
							"Kuwaiti",
							"Jordanian",
							"Syrian",
							"Iranian",
							"Afghan",
							"Kazakh",
							"Georgian",
							"Armenian",
							"Lithuanian",
							"Latvian",
							"Estonian",
							"Belarusian",
							"Moldovan",
							"Slovak",
							"Slovenian",
							"Austrian",
							"Swiss",
							"Icelandic",
							"Maltese",
							"Cypriot",
							"Albanian",
							"Bosnian",
							"Macedonian",
							"Montenegrin",
							"Chilean",
							"Ecuadorian",
							"Paraguayan",
							"Uruguayan",
							"Bolivian",
							"Honduran",
							"Nicaraguan"
						]
					},
					"ethnicity": {
						"type": "string",
						"description": "Character ethnicity",
						"enum": [
							"Caucasian / White",
							"Latina / Hispanic",
							"Black / African-American",
							"Asian (East Asian)",
							"Mixed / Biracial",
							"Brazilian",
							"Russian / Slavic",
							"Colombian",
							"Ukrainian",
							"Japanese",
							"Filipina",
							"Thai",
							"Korean",
							"Mexican",
							"Ebony / West African",
							"Chinese",
							"Indian (South Asian)",
							"Vietnamese",
							"Middle Eastern / Arab",
							"Mediterranean (Greek/Italian/Spanish)",
							"Scandinavian / Nordic",
							"Venezuelan",
							"Dominican",
							"Argentine",
							"Peruvian",
							"Eastern European",
							"Puerto Rican",
							"Cuban",
							"Asian-Caucasian Mix",
							"Black-Caucasian Mix",
							"Latina-Caucasian Mix",
							"Pawg (White with big assets)",
							"BBC-adjacent Black",
							"Light-skin Black",
							"Caramel / Latina",
							"Olive / Mediterranean",
							"Pale / Porcelain White",
							"Tanned / Beach Latina",
							"Redhead / Ginger",
							"Blonde Scandinavian",
							"Brunette European",
							"Jewish (Ashkenazi)",
							"Persian / Iranian",
							"Turkish",
							"Lebanese",
							"Armenian",
							"Native American",
							"Pacific Islander",
							"Polynesian / Samoan",
							"Moroccan / North African",
							"Egyptian",
							"Algerian",
							"Nigerian",
							"Ghanaian",
							"Kenyan",
							"South African (White)",
							"South African (Coloured)",
							"Jamaican",
							"Trinidadian",
							"Barbadian",
							"Punjabi / North Indian",
							"Tamil / South Indian",
							"Bengali",
							"Pakistani",
							"Indonesian",
							"Malaysian",
							"Singaporean Chinese",
							"Taiwanese",
							"Hong Kong Chinese",
							"Eurasian (Asian + White)",
							"Blasian (Black + Asian)",
							"Afro-Latina",
							"Arab",
							"Kurdish",
							"Greek",
							"Italian",
							"Spanish",
							"Portuguese",
							"French",
							"German",
							"Dutch",
							"Polish",
							"Czech",
							"Romanian",
							"Hungarian",
							"Serbian",
							"Croatian",
							"Bulgarian",
							"Albanian",
							"Bosnian",
							"Gypsy / Romani",
							"Maori",
							"Aboriginal Australian",
							"Hmong",
							"Cambodian",
							"Lao",
							"Mongolian",
							"Kazakh",
							"Uyghur",
							"Afghan / Pashtun"
						]
					},
					"dateOfBirth": {
						"type": "string",
						"description": "Date of birth. Character must be 21+. If not provided, defaults to 27 years old.",
						"example": "1995-06-15"
					},
					"firstName": {
						"type": "string",
						"description": "Character first name"
					},
					"lastName": {
						"type": "string",
						"description": "Character last name"
					},
					"biography": {
						"type": "string",
						"description": "Character biography/backstory"
					},
					"alias": {
						"type": "string",
						"description": "Character alias/nickname"
					},
					"job": {
						"type": "string",
						"description": "Specific job title"
					},
					"whereYouLive": {
						"type": "string",
						"description": "Location where character lives"
					},
					"gender": {
						"type": "string",
						"description": "Character gender"
					},
					"orientation": {
						"type": "string",
						"description": "Character's sexual orientation. Affects who the character is attracted to in text conversations.",
						"enum": [
							"Straight",
							"Bisexual",
							"Mostly straight",
							"Bi-curious",
							"Pansexual",
							"Lesbian",
							"Heteroflexible",
							"Bicurious (leans men)",
							"Bicurious (leans women)",
							"Demisexual",
							"Queer",
							"Mostly lesbian",
							"Homoflexible",
							"Asexual (but enjoys sex with partner)",
							"Fluid",
							"Gay (Men)",
							"Experimenting",
							"Open to anything",
							"Straight but plays with girls",
							"90/10 (mostly straight)"
						]
					},
					"sexualExperience": {
						"type": "string",
						"description": "Character's sexual experience level. Affects the character's personality and how they discuss sexual topics in text conversations.",
						"enum": [
							"Virgin",
							"Almost virgin",
							"Very limited",
							"Moderate",
							"Experienced",
							"Very experienced",
							"Highly experienced",
							"100+ partners",
							"Former escort",
							"Nymphomaniac",
							"Only relationships",
							"Mostly one-night stands",
							"Lots of threesomes",
							"Swinger",
							"Porn-level",
							"Innocent but curious",
							"Sheltered then wild",
							"Reformed good girl",
							"Always slutty",
							"Recently awakened"
						]
					},
					"kinks": {
						"type": "array",
						"description": "Character's sexual preferences and kinks. Used to shape the character's personality and behavior in text conversations only — they do not control image or video generation capabilities.",
						"items": {
							"type": "string",
							"enum": [
								"Daddy kink / DDLG",
								"Breeding / creampie",
								"Light bondage",
								"Spanking",
								"Choking / breath play",
								"Anal play",
								"Degradation",
								"Praise kink",
								"CNC / consensual non-consent",
								"Petplay",
								"Exhibitionism",
								"Voyeurism",
								"Free use",
								"Orgasm control / denial",
								"Overstimulation",
								"Size queen",
								"Cuckquean / hotpast",
								"Pegging",
								"Foot fetish",
								"Roleplay",
								"Mommy kink",
								"Ageplay (legal adults only)",
								"Raceplay",
								"Impact play",
								"Wax play",
								"Public / semi-public",
								"Gangbang fantasy",
								"Double penetration",
								"Squirting focus",
								"Edging",
								"Watersports",
								"Findom",
								"Chastity / keyholding",
								"Latex / leather",
								"Bimbofication",
								"Corruption kink"
							]
						}
					},
					"sexPositions": {
						"type": "array",
						"description": "Character's preferred sex positions. These define the character's personality and preferences for text conversations only — they do not control image or video generation capabilities.",
						"items": {
							"type": "string",
							"enum": [
								"Doggy style",
								"Missionary",
								"Cowgirl",
								"Reverse cowgirl",
								"Spooning",
								"Prone bone",
								"69",
								"Face-sitting",
								"Standing doggy",
								"Lotus",
								"Butterfly (edge of bed)",
								"Deep missionary (legs on shoulders)",
								"Blowjob (kneeling)",
								"Anal doggy",
								"Seated cowgirl / lap sex",
								"The Hot Seat (reverse lap)",
								"Pillow-under-hips missionary",
								"Full nelson",
								"Piledriver",
								"Double penetration",
								"Tit job",
								"Side-by-side / lazy doggy",
								"Shower sex (standing doggy)",
								"Couch doggy",
								"Amazon position",
								"Side-by-side sex",
								"Car sex cowgirl",
								"Lazy spooning",
								"Deep missionary ankles locked",
								"Finish-in-mouth kneeling blowjob"
							]
						}
					},
					"relationshipStyle": {
						"type": "string",
						"description": "Character's relationship dynamic with the user. Affects how the character behaves and relates to the user in text conversations.",
						"enum": [
							"Monogamous girlfriend",
							"Devoted housewife",
							"Free-use 24/7",
							"Stay-at-home girlfriend",
							"Open relationship",
							"Trophy girlfriend",
							"Submissive girlfriend",
							"Dominant girlfriend",
							"Casual FWB",
							"Sugar baby",
							"Collared & owned",
							"Hotwife",
							"Cuckquean",
							"Polyamorous",
							"Secret affair",
							"Traditional 1950s",
							"Brat to be tamed",
							"Fiancée",
							"Live-in slut",
							"Long-distance girlfriend"
						]
					},
					"relationshipStatus": {
						"type": "string",
						"description": "Character's relationship status. Part of the character's backstory, affects text conversation context.",
						"enum": [
							"Single",
							"In a relationship",
							"Engaged",
							"Married",
							"Divorced",
							"Widowed",
							"It's complicated",
							"Open relationship",
							"Situationship",
							"Single and looking",
							"Single not looking",
							"Taken but bored",
							"Friends with benefits",
							"Dating around",
							"Exclusively yours"
						]
					},
					"interests": {
						"type": "array",
						"description": "Character interests",
						"items": {
							"type": "string",
							"enum": [
								"Dancing",
								"Yoga",
								"Work",
								"Gaming",
								"Streaming",
								"Cosplay",
								"Anime",
								"Nature",
								"Animals",
								"Hiking",
								"Spirituality",
								"Exercise",
								"Reading",
								"Travelling",
								"Chess",
								"Tattoos",
								"Art",
								"Drawing",
								"Music",
								"Gym",
								"Running",
								"Tanning",
								"Dining out",
								"Cooking",
								"Partying",
								"Sports",
								"Magic",
								"Museums",
								"History",
								"Writing",
								"Watching movies",
								"Golf",
								"Tennis",
								"Photography",
								"Astrology",
								"Nightlife",
								"Adventure",
								"Fashion",
								"Soccer",
								"Theatre",
								"Cycling",
								"Exercising",
								"Business",
								"Clean eating",
								"The outdoors",
								"Baking",
								"Piano",
								"Charity",
								"Wine",
								"Restaurants",
								"Tech",
								"Psychology",
								"Meditation",
								"Chatting",
								"Gardening",
								"Painting",
								"Swimming",
								"Fitness",
								"Volleyball",
								"Movies",
								"Skateboarding",
								"Farming",
								"Climbing",
								"Camping",
								"Sailing",
								"Scuba diving",
								"Weightlifting",
								"Gymnastics",
								"Mixed martial arts",
								"Pole dancing",
								"Wine tasting",
								"Mountaineering",
								"Rowing",
								"Hockey",
								"Judo",
								"Manga",
								"Beach activities",
								"Digital art",
								"Football",
								"Motorcycles",
								"DIY",
								"Science",
								"Technology",
								"Shooting",
								"Driving",
								"Exploring",
								"Boxing",
								"Comedy",
								"Cars",
								"Rock music",
								"Jazz",
								"Shopping",
								"Health",
								"Stocks",
								"Films",
								"Basketball",
								"Dance",
								"Dogs",
								"Creativity",
								"Journalism",
								"Clubbing",
								"Raves",
								"Cheerleading",
								"Filmmaking",
								"Beach",
								"Travel",
								"Netflix",
								"Video games",
								"Home design",
								"Social media",
								"Acting",
								"Horses",
								"Beauty",
								"True crime"
							]
						}
					},
					"personality": {
						"type": "string",
						"description": "Character's personality archetype. Shapes the character's overall behavior and tone in text conversations.",
						"enum": [
							"Sweet & innocent",
							"Bratty",
							"Bubbly / genki",
							"Shy & submissive",
							"Flirty tease",
							"Tsundere",
							"Yandere",
							"Confident boss babe",
							"Nerdy / gamer girl",
							"Spoiled princess",
							"Girl-next-door",
							"Ice queen (melts for you)",
							"Himbo-level ditzy",
							"Dominant & teasing",
							"Obsessed / clingy",
							"Low-maintenance chill",
							"High-maintenance diva",
							"Gothic / alt girl",
							"Bookworm / quiet intellectual",
							"Hyper horny nympho",
							"Reformed good girl",
							"Total bimbo",
							"Caring mommy vibe",
							"Sassy & sarcastic",
							"Daddy's little princess"
						]
					},
					"sexDrive": {
						"type": "string",
						"description": "Character's sex drive level. Affects the character's eagerness and initiative in text conversations.",
						"enum": [
							"Very high",
							"Hypersexual nympho",
							"High",
							"Always horny",
							"Average",
							"Touch-starved",
							"Insatiable",
							"High but shy",
							"Moderate",
							"Low until aroused then wild",
							"Once a day minimum",
							"Morning sex addict",
							"Only horny when ovulating",
							"Switches normal to crazy",
							"Low",
							"Very low cuddly",
							"Asexual but enjoys pleasing",
							"Permanently in heat",
							"Night owl horny",
							"Needs it to fall asleep"
						]
					},
					"conversationStyle": {
						"type": "string",
						"description": "Character's conversation style. Controls the tone and manner of the character's text responses.",
						"enum": [
							"Flirty & teasing",
							"Sweet & innocent",
							"Bratty & sassy",
							"Shy & soft-spoken",
							"Bubbly & giggly",
							"Dirty talk queen",
							"Proper & polite",
							"Valley girl",
							"Weeb / anime speech",
							"Tsundere",
							"Baby-talk / little space",
							"Foul-mouthed & trashy",
							"Sarcastic & witty",
							"Submissive & obedient",
							"Dominant & commanding",
							"Country / southern drawl",
							"Nerdy & rambling",
							"High-maintenance princess",
							"Low-key & chill",
							"Constant compliments & praise"
						]
					},
					"attitude": {
						"type": "string",
						"description": "Character's attitude. Influences the character's demeanor in text conversations.",
						"enum": [
							"Sweet & innocent",
							"Bratty",
							"Bubbly & cheerful",
							"Shy & submissive",
							"Flirty tease",
							"Tsundere",
							"Yandere",
							"Spoiled princess",
							"Confident boss babe",
							"Nerdy gamer girl",
							"Ice queen",
							"Sassy & sarcastic",
							"Clingy & obsessed",
							"Dominant & commanding",
							"Girl-next-door",
							"Gothic / alt",
							"Ditzy bimbo",
							"Caring mommy",
							"High-maintenance diva",
							"Low-key chill"
						]
					},
					"height": {
						"type": "string",
						"description": "Character height",
						"example": "5'7\""
					},
					"bodyBuild": {
						"type": "string",
						"description": "Body build type",
						"enum": [
							"Slim",
							"Petite",
							"Athletic",
							"Toned",
							"Curvy",
							"Hourglass",
							"Slender",
							"Fit",
							"Thick",
							"BBW",
							"Skinny",
							"Muscular",
							"Chubby",
							"Voluptuous",
							"Lean",
							"Model-thin",
							"Pear shape",
							"Busty athletic",
							"Soft & curvy",
							"Amazon / tall & strong",
							"Buxom",
							"Average",
							"Busty",
							"Slim, athletic,",
							"Busty hourglass",
							"Athletic-Curvy,",
							"Petite hourglass",
							"Tall, Athletic Hourglass",
							"Muscular-athletic",
							"Athletic-Curvy"
						]
					},
					"bodyShape": {
						"type": "string",
						"description": "Body shape",
						"enum": [
							"Hourglass",
							"Pear",
							"Apple",
							"Rectangle",
							"Inverted triangle",
							"Slim hourglass",
							"Thick hourglass",
							"Bottom-heavy pear",
							"Top-heavy",
							"Balanced curvy",
							"Athletic rectangle",
							"Soft pear",
							"Petite hourglass",
							"Busty rectangle",
							"Bubble butt pear",
							"Slim-thick",
							"Coke-bottle",
							"Shelf booty",
							"Voluptuous hourglass",
							"Chubby apple"
						]
					},
					"buttSize": {
						"type": "string",
						"description": "Butt size",
						"enum": [
							"Big",
							"Round & full",
							"Bubble butt",
							"Thick",
							"Jiggly",
							"Huge",
							"Average",
							"Small",
							"Shelf-like",
							"Peach",
							"Juicy",
							"Massive",
							"Firm & round",
							"Flat",
							"Phat",
							"Heart-shaped",
							"Perky",
							"Wide"
						]
					},
					"hairLength": {
						"type": "string",
						"description": "Hair length",
						"enum": [
							"Long (mid-back to waist)",
							"Very long (waist to hips)",
							"Shoulder-length",
							"Extra long (classic length – below butt)",
							"Medium / lob (long bob, just past shoulders)",
							"Short bob",
							"Chin-length bob",
							"Pixie cut",
							"Butt-length",
							"Chest / bra-strap length",
							"Hip-length",
							"Short layered",
							"Tailbone length",
							"Shaved sides + long top",
							"Bald / completely shaved",
							"Buzzcut",
							"Asymmetrical bob",
							"Undercut",
							"Mid-back",
							"Knee-length (extreme rare)",
							"Floor-length (extreme fantasy)",
							"Short crop",
							"Pageboy",
							"Shag / wolf cut",
							"Mullet (modern sexy version)"
						]
					},
					"hairColour": {
						"type": "string",
						"description": "Hair colour",
						"enum": [
							"Black",
							"Dark Brown",
							"Blonde",
							"Light Brown",
							"Platinum Blonde",
							"Jet Black",
							"Golden Blonde",
							"Honey Blonde",
							"Strawberry Blonde",
							"Auburn",
							"Chestnut Brown",
							"Ash Blonde",
							"Caramel Brown",
							"Chocolate Brown",
							"Dirty Blonde",
							"Natural Red / Ginger",
							"Raven Black",
							"Sandy Blonde",
							"Rose Gold",
							"Silver / Grey",
							"Pastel Pink",
							"Ombré (dark to blonde)",
							"Balayage Blonde",
							"Copper Red",
							"Violet / Purple",
							"Blue",
							"White Blonde",
							"Burgundy",
							"Ice Blonde",
							"Butter Blonde",
							"Bronde",
							"Champagne Blonde",
							"Smokey Lilac",
							"Fiery Red",
							"Mahogany",
							"Green",
							"Dark Auburn",
							"Ash Brown",
							"Peach",
							"Lavender"
						]
					},
					"eyeColour": {
						"type": "string",
						"description": "Eye colour",
						"enum": [
							"Blue",
							"Hazel",
							"Green",
							"Brown / Dark Brown",
							"Grey",
							"Light Blue",
							"Ice Blue / Pale Blue",
							"Amber / Golden",
							"Heterochromia (two different colors)",
							"Dark Brown (almost black)",
							"Emerald Green",
							"Honey Brown",
							"Grey-Blue",
							"Violet / Purple (real or contacts)",
							"Aqua / Turquoise",
							"Grey-Green",
							"Central Heterochromia (e.g., brown with green ring)",
							"Deep Green",
							"Sky Blue",
							"Steel Grey",
							"Golden Brown",
							"Red / Albino",
							"Cat-like Yellow/Gold",
							"Sectoral Heterochromia (pie slice)",
							"Bright Green"
						]
					},
					"skinTone": {
						"type": "string",
						"description": "Skin tone",
						"enum": [
							"Light / Fair",
							"Tan / Golden",
							"Olive / Mediterranean",
							"Caramel / Light Brown",
							"Medium Brown",
							"Deep Tan / Bronzed",
							"Porcelain / Very Pale",
							"Deep Brown / Chocolate",
							"Ebony / Very Dark",
							"Honey",
							"Beige",
							"Warm Ivory",
							"Golden Olive",
							"Light Caramel",
							"Mocha",
							"Rich Brown",
							"Sun-Kissed",
							"Alabaster",
							"Latte",
							"Mahogany",
							"Warm Honey",
							"Light Olive",
							"Tawny",
							"Espresso",
							"Pale Ivory"
						]
					},
					"breastSize": {
						"type": "string",
						"description": "Breast size",
						"enum": [
							"C cup",
							"D cup",
							"DD / E cup",
							"B cup",
							"Natural C",
							"Perky D",
							"Big natural (DD–F)",
							"Small / A–B",
							"Large / F–G",
							"Fake / Obviously augmented",
							"Full C",
							"Perfect handful (firm B–C)",
							"Huge / H+",
							"Petite / Flat-to-A",
							"Bolt-ons (fake D–DD)",
							"Teardrop implants",
							"Very perky C",
							"Full DD",
							"Small & perky",
							"Massive naturals (G–H)",
							"Athletic small (A–B)",
							"Round fake DD",
							"Tiny / AA–A",
							"Oversized implants (H–K)",
							"Soft & full D"
						]
					},
					"breastPertness": {
						"type": "string",
						"description": "Breast pertness",
						"enum": [
							"Perky",
							"Firm & perky",
							"Natural perky",
							"Full & perky",
							"Slightly pendulous (natural hang)",
							"Very perky / youthful",
							"Teardrop shape",
							"Round & firm",
							"Soft & natural",
							"Augmented / fake perky",
							"Perfect teardrop",
							"High & firm",
							"Full but soft",
							"Athletic / toned & perky",
							"Gently sloping",
							"Heavy & full (natural sag)",
							"Bolt-on / obviously fake round",
							"Puffy nipples + perky",
							"East-West (point outward)",
							"Slightly sagging (post-baby natural)",
							"Perfectly round implants",
							"Torpedo / tubular",
							"Wide-set & perky",
							"Small & super perky",
							"Mature / naturally pendulous"
						]
					},
					"nippleColour": {
						"type": "string",
						"description": "Nipple colour",
						"enum": [
							"Light pink",
							"Pink",
							"Rose / Medium pink",
							"Dark pink",
							"Light brown",
							"Medium brown",
							"Dark brown",
							"Pale pink (almost white)",
							"Rosy brown",
							"Deep brown / Chocolate",
							"Almost black",
							"Peachy pink",
							"Mauve",
							"Tan brown",
							"Reddish-pink",
							"Dusky rose",
							"Caramel brown",
							"Puffy light pink",
							"Puffy dark pink",
							"Inverted (dark pink/brown)"
						]
					},
					"vaginaHair": {
						"type": "string",
						"description": "Vagina hair style",
						"enum": [
							"Completely shaved / bald",
							"Landing strip",
							"Smooth wax (Brazilian – everything off)",
							"Neat triangle",
							"Small trimmed patch",
							"Hollywood (100 % bare)",
							"Thin landing strip",
							"Full bush (natural 70s style)",
							"Trimmed short / low maintenance",
							"Heart shape",
							"Brazilian with tiny strip",
							"Natural but shaped",
							"Dyed to match hair",
							"Arrow / V shape",
							"Partially shaved sides",
							"Lightning bolt",
							"Au naturel / untouched bush",
							"Short & curly (Afro-textured)",
							"Thin vertical strip",
							"Completely natural long",
							"Diamond shape",
							"French wax (strip + lips bare)",
							"Designer (letters/symbols)",
							"Colored bush (pink/blue/etc.)",
							"Wild & untrimmed"
						]
					},
					"vaginaSize": {
						"type": "string",
						"description": "Vagina size",
						"enum": [
							"Tight",
							"Very tight",
							"Super tight",
							"Snug",
							"Average",
							"Petite tight",
							"Extremely tight",
							"Youthfully tight",
							"Slightly looser",
							"Perfectly snug",
							"Tight but accommodating",
							"Incredibly tight",
							"Normal",
							"Loose & wet",
							"Very accommodating",
							"Post-baby looser",
							"Gripping",
							"Relaxed",
							"Well-used",
							"Milf-level relaxed"
						]
					},
					"additionalDescription": {
						"type": "string",
						"maxLength": 1000,
						"description": "Free-text traits added to the reference image that the structured fields can't express. Examples: `nose piercing`, `hooped earrings`, `cherry blossom sleeve tattoo`, `wire-frame glasses`, `thin scar above the left eyebrow`.",
						"example": "six months pregnant, small dragon tattoo across the right hip, wire-frame glasses, a thin scar above the left eyebrow"
					}
				}
			},
			"SaveAiCharacterRequest": {
				"type": "object",
				"required": ["characterId"],
				"properties": {
					"characterId": {
						"type": "string",
						"format": "uuid",
						"description": "Character ID from generation step"
					},
					"b2bClientId": {
						"type": "string",
						"description": "B2B client ID (required if ohFunCreatorId not provided)"
					},
					"ohFunCreatorId": {
						"type": "string",
						"description": "OhFun creator ID (required if b2bClientId not provided)"
					},
					"firstName": {
						"type": "string",
						"description": "Override first name"
					},
					"lastName": {
						"type": "string",
						"description": "Override last name"
					},
					"biography": {
						"type": "string",
						"description": "Override biography"
					},
					"alias": {
						"type": "string",
						"description": "Override alias/nickname"
					},
					"job": {
						"type": "string",
						"description": "Override job title"
					},
					"whereYouLive": {
						"type": "string",
						"description": "Override location"
					},
					"dateOfBirth": {
						"type": "string",
						"description": "Override date of birth. Character must be 21+.",
						"example": "1995-06-15"
					},
					"gender": {
						"type": "string",
						"description": "Override gender"
					},
					"orientation": {
						"type": "string",
						"description": "Override orientation"
					},
					"relationshipStatus": {
						"type": "string",
						"description": "Override relationship status"
					},
					"interests": {
						"$ref": "#/components/schemas/GenerateAiCharacterRequest/properties/interests"
					},
					"personality": {
						"type": "string",
						"description": "Override personality"
					},
					"typeOfCharacter": {
						"type": "string",
						"description": "Type of character for OhChat"
					},
					"bodyType": {
						"type": "number",
						"description": "Body type ID for OhChat"
					},
					"eyeColor": {
						"type": "number",
						"description": "Eye color ID for OhChat"
					},
					"hairColor": {
						"type": "number",
						"description": "Hair color ID for OhChat"
					},
					"messagePrice": {
						"type": "number",
						"description": "Price per message"
					},
					"subscriptionPrice": {
						"type": "number",
						"description": "Subscription price"
					},
					"uid": {
						"type": "number",
						"description": "User ID in OhChat/Drupal system"
					}
				}
			}
		}
	}
}
