{"openapi":"3.1.0","info":{"title":"Phota API","description":"Create and manage profiles for photo editing and enhancement.","version":"v0.7.0"},"paths":{"/v1/phota/edit":{"post":{"tags":["Studio"],"summary":"Edit","description":"Edit images based on a text prompt.\n\nProvide input images (base64 or public URL) and a prompt describing the\ndesired edit. Returns base64 images or signed download URLs.","operationId":"edit_v1_phota_edit_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditResponse"}}}},"401":{"description":"Invalid API key in the X-API-Key header."},"403":{"description":"Missing X-API-Key header (no API key provided)."},"402":{"description":"Insufficient prepaid credit balance."},"400":{"description":"Invalid request or content moderation violation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError400Response"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError404Response"}}}},"500":{"description":"Internal error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError500Response"}}}},"503":{"description":"Service temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError503Response"}}}},"202":{"description":"Async job accepted; poll job_id at poll_url.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobHandle"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/v1/phota/generate":{"post":{"tags":["Studio"],"summary":"Generate","description":"Generate images from a text prompt without input images.\n\nProvide a prompt describing the desired image. Returns base64 images or\nsigned download URLs.","operationId":"generate_v1_phota_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditResponse"}}}},"401":{"description":"Invalid API key in the X-API-Key header."},"403":{"description":"Missing X-API-Key header (no API key provided)."},"402":{"description":"Insufficient prepaid credit balance."},"400":{"description":"Invalid request or content moderation violation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError400Response"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError404Response"}}}},"500":{"description":"Internal error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError500Response"}}}},"503":{"description":"Service temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError503Response"}}}},"202":{"description":"Async job accepted; poll job_id at poll_url.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobHandle"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/v1/phota/enhance":{"post":{"tags":["Studio"],"summary":"Enhance","description":"Enhance a single image.\n\nEnhancement parameters are inferred automatically. Optionally provide\na text prompt to guide the enhancement. Returns images or signed download URLs.","operationId":"enhance_v1_phota_enhance_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnhanceRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditResponse"}}}},"401":{"description":"Invalid API key in the X-API-Key header."},"403":{"description":"Missing X-API-Key header (no API key provided)."},"402":{"description":"Insufficient prepaid credit balance."},"400":{"description":"Invalid request or content moderation violation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError400Response"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError404Response"}}}},"500":{"description":"Internal error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError500Response"}}}},"503":{"description":"Service temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError503Response"}}}},"202":{"description":"Async job accepted; poll job_id at poll_url.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobHandle"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/v1/phota/remix":{"post":{"tags":["Studio"],"summary":"Remix","description":"Restyle an image to match a reference image.\n\nProvide an input image and a reference image; the output keeps the\ninput's content rendered in the reference's style. Optional profile IDs\npreserve recognized identities, and an optional prompt steers the remix.\nOutput resolution is fixed at 2K. Returns base64 images or signed\ndownload URLs.","operationId":"remix_v1_phota_remix_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemixRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditResponse"}}}},"401":{"description":"Invalid API key in the X-API-Key header."},"403":{"description":"Missing X-API-Key header (no API key provided)."},"402":{"description":"Insufficient prepaid credit balance."},"400":{"description":"Invalid request or content moderation violation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError400Response"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError404Response"}}}},"500":{"description":"Internal error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError500Response"}}}},"503":{"description":"Service temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError503Response"}}}},"202":{"description":"Async job accepted; poll job_id at poll_url.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobHandle"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/v1/phota/jobs/{job_id}":{"get":{"tags":["Studio"],"summary":"Get Job Status","description":"Poll an async edit/generate/enhance/remix job's status.","operationId":"get_job_status_v1_phota_jobs__job_id__get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatus"}}}},"401":{"description":"Invalid API key in the X-API-Key header."},"403":{"description":"Missing X-API-Key header (no API key provided)."},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError404Response"}}}},"410":{"description":"Job result has expired."},"500":{"description":"Internal error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError500Response"}}}},"503":{"description":"Service temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError503Response"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/phota/webhook-secret":{"get":{"tags":["Studio"],"summary":"Get Webhook Secret","description":"Return the authenticated developer's webhook signing secret.\n\nThis is the per-developer HMAC key used to sign the ``X-Phota-Signature``\nheader on async job callbacks; partners fetch it once to verify callback\nauthenticity. The secret is minted lazily on first use. Scoped strictly to\nthe authenticated developer (the API key yields the developer uid).","operationId":"get_webhook_secret_v1_phota_webhook_secret_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSecretResponse"}}}},"401":{"description":"Invalid API key in the X-API-Key header."},"403":{"description":"Missing X-API-Key header (no API key provided)."}},"security":[{"APIKeyHeader":[]}]}},"/v1/phota/profiles/ids":{"get":{"tags":["Profiles"],"summary":"Get Profile Ids","description":"List all profiles for the authenticated user, optionally filtered by tag.","operationId":"get_profile_ids_v1_phota_profiles_ids_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":128},{"type":"null"}],"description":"Filter profiles by tag. Omit to return all profiles.","title":"Tag"},"description":"Filter profiles by tag. Omit to return all profiles."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileIdsResponse"}}}},"401":{"description":"Invalid API key in the X-API-Key header."},"403":{"description":"Missing X-API-Key header (no API key provided)."},"402":{"description":"Insufficient prepaid credit balance."},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError404Response"}}}},"500":{"description":"Internal error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError500Response"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/phota/profiles/{profile_id}/status":{"get":{"tags":["Profiles"],"summary":"Get Profile Status","description":"Get the status of a profile.","operationId":"get_profile_status_v1_phota_profiles__profile_id__status_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileStatusResponse"}}}},"401":{"description":"Invalid API key in the X-API-Key header."},"403":{"description":"Missing X-API-Key header (no API key provided)."},"402":{"description":"Insufficient prepaid credit balance."},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError404Response"}}}},"500":{"description":"Internal error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError500Response"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/phota/profiles/{profile_id}/profile_picture":{"get":{"tags":["Profiles"],"summary":"Get Profile Picture","description":"Return the profile picture as raw JPEG image bytes.","operationId":"get_profile_picture_v1_phota_profiles__profile_id__profile_picture_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}}],"responses":{"200":{"description":"Profile picture as JPEG image.","content":{"image/jpeg":{}}},"401":{"description":"Invalid API key in the X-API-Key header."},"403":{"description":"Missing X-API-Key header (no API key provided)."},"402":{"description":"Insufficient prepaid credit balance."},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError404Response"}}}},"500":{"description":"Internal error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError500Response"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/phota/profiles/add":{"post":{"tags":["Profiles"],"summary":"Add Profile","description":"Create a new profile from signed image URLs.\n\nStarts training asynchronously and returns a profile ID immediately.\nPoll `GET /profiles/{profile_id}/status` to check when training completes.","operationId":"add_profile_v1_phota_profiles_add_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddProfileRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddProfileResponse"}}}},"401":{"description":"Invalid API key in the X-API-Key header."},"403":{"description":"Missing X-API-Key header (no API key provided)."},"402":{"description":"Insufficient prepaid credit balance."},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError404Response"}}}},"500":{"description":"Internal error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError500Response"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/v1/phota/profiles/{profile_id}":{"delete":{"tags":["Profiles"],"summary":"Delete Profile","description":"Permanently delete a profile and all associated data.","operationId":"delete_profile_v1_phota_profiles__profile_id__delete","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteProfileResponse"}}}},"401":{"description":"Invalid API key in the X-API-Key header."},"403":{"description":"Missing X-API-Key header (no API key provided)."},"402":{"description":"Insufficient prepaid credit balance."},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError404Response"}}}},"500":{"description":"Internal error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotaError500Response"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AddProfileRequest":{"properties":{"image_urls":{"items":{"type":"string"},"type":"array","maxItems":50,"minItems":5,"title":"Image Urls","description":"Publicly accessible URLs pointing to the training images. Supported formats: JPEG, PNG, WebP, and HEIC/HEIF. Images larger than 4K (4096px) are automatically resized; for best results, provide images at 4K or below. PNG images are transcoded to JPEG (quality 95) for storage efficiency. Image-count range depends on training_tier: \"standard\" requires 10–50, \"fast\" requires 5–10."},"training_tier":{"$ref":"#/components/schemas/TrainingTier","description":"Training tier for the profile. \"standard\" trains on 10–50 images for highest quality; \"fast\" trains on 5–10 images for quicker turnaround at lower cost.","default":"standard"},"tag":{"anyOf":[{"type":"string","maxLength":128,"minLength":1},{"type":"null"}],"title":"Tag","description":"Optional immutable tag to group or namespace this profile (e.g., your end-user's identifier)."}},"type":"object","required":["image_urls"],"title":"AddProfileRequest","description":"Request body for creating a new profile.","examples":[{"image_urls":["https://example.com/photo1.jpg","https://storage.googleapis.com/bucket/photo2.jpg?X-Goog-Signature=..."],"tag":"user_abc123"},{"image_urls":["https://example.com/photo1.jpg","https://example.com/photo2.jpg","https://example.com/photo3.jpg","https://example.com/photo4.jpg","https://example.com/photo5.jpg","https://example.com/photo6.jpg"],"tag":"user_abc123","training_tier":"fast"}]},"AddProfileResponse":{"properties":{"profile_id":{"type":"string","title":"Profile Id","description":"Unique identifier for the new profile. Use this to poll training status."}},"type":"object","required":["profile_id"],"title":"AddProfileResponse","description":"Response returned after a profile is created."},"DeleteProfileResponse":{"properties":{"profile_id":{"type":"string","title":"Profile Id","description":"Identifier of the deleted profile."},"status":{"type":"string","title":"Status","description":"Result status, e.g. 'ok'."}},"type":"object","required":["profile_id","status"],"title":"DeleteProfileResponse","description":"Response confirming a profile has been permanently deleted."},"EditJobErrorCode":{"type":"string","enum":["instance_died","dispatch_failed"],"title":"EditJobErrorCode","description":"Job-infrastructure failure codes with no synchronous-pipeline equivalent.\n\nPipeline failures reuse the synchronous customer-facing taxonomy\n(``api.schema.phota.PhotaErrorCode``) so an async failure surfaces the same\nstable code as its sync twin. These members cover the failure modes the\nwatchdog sweep raises, which the sync path never produces:\n\nAttributes:\n    INSTANCE_DIED: A ``RUNNING`` job was reaped after exceeding max runtime\n        (the instance crashed mid-run).\n    DISPATCH_FAILED: A ``PENDING`` job's dispatch never landed after the\n        maximum re-dispatch attempts."},"EditRequest":{"properties":{"async":{"type":"boolean","title":"Async","description":"Submit asynchronously. Returns 202 with a job_id to poll at GET /v1/jobs/{job_id} instead of holding the connection until the image is ready.","default":false},"callback_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callback Url","description":"Optional https URL we POST the signed result to on completion. Best-effort (polling remains authoritative). Requires async=true."},"client_request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Request Id","description":"Idempotency key. A retried submit with the same value returns the original job instead of creating (and charging for) a new one."},"response_mode":{"type":"string","enum":["bytes","urls"],"title":"Response Mode","description":"Response delivery mode. 'bytes': return base64-encoded image data (default). 'urls': return signed download URLs (24-hour expiry) instead of image bytes.","default":"bytes"},"prompt":{"type":"string","title":"Prompt","description":"Text prompt describing the desired edit.","default":""},"images":{"items":{"type":"string"},"type":"array","maxItems":10,"title":"Images","description":"Input images as raw base64 strings or publicly accessible URLs. Both formats are auto-detected. Supported formats: JPEG, PNG, WebP, and HEIC/HEIF. Images larger than 4K (4096px) are automatically resized; for best results, provide images at 4K or below."},"profile_ids":{"items":{"type":"string"},"type":"array","title":"Profile Ids","description":"Subset of your account's profiles relevant to this request. Your account holds all profiles, but only pass the ones belonging to the end-user whose photo is being edited. These profiles serve as candidates for identity preservation. The fewer profiles you pass, the easier it is for our system to determine which profiles are relevant."},"num_output_images":{"type":"integer","maximum":4.0,"minimum":1.0,"title":"Num Output Images","description":"Number of output images to generate (1-4).","default":1},"aspect_ratio":{"type":"string","enum":["auto","1:1","3:4","4:3","9:16","16:9"],"title":"Aspect Ratio","description":"Output aspect ratio (auto, 1:1, 3:4, 4:3, 9:16, 16:9).","default":"auto"},"resolution":{"type":"string","enum":["1K","2K","4K"],"title":"Resolution","description":"Output resolution (1K, 2K, 4K). Per-model support varies; see /models.","default":"1K"},"quality":{"anyOf":[{"type":"string","enum":["auto","low","medium","high"]},{"type":"null"}],"title":"Quality","description":"Quality tier (auto, low, medium, high). Only supported by gpt-image-2; passing this field for other models returns 400. Omit (or pass 'auto') to let the provider pick — for gpt-image-2 this resolves to OpenAI's auto tier. Lower tiers reduce output tokens and therefore settled cost."},"base_model":{"type":"string","enum":["flux-2","gpt-image-2","nb2","qwen-image-2","reve"],"title":"Base Model","description":"Base model identifier. Omit to use the default base model. Unknown ids are rejected with 400; base models that do not support this endpoint's capability are also rejected with 400.","default":"nb2"},"output_format":{"type":"string","enum":["png","jpg"],"title":"Output Format","description":"Output image format (png, jpg). Default: png (will change to jpg on 2026-05-08).","default":"png"}},"type":"object","title":"EditRequest","description":"Request body for the edit endpoint.\n\nInput images can be provided as either raw base64-encoded bytes or\npublicly accessible URLs (e.g. ``https://example.com/photo.jpg``).\nBoth formats are accepted and detected automatically.","examples":[{"images":["iVBORw0KGgo...","https://example.com/photo.jpg"],"num_output_images":2,"profile_ids":["abc123","def456"],"prompt":"Turn [[def456]] into a vampire"}]},"EditResponse":{"properties":{"images":{"items":{"type":"string"},"type":"array","title":"Images","description":"Output image(s) as raw base64-encoded strings. Format matches the requested output_format. Populated when response_mode='bytes', empty otherwise."},"download_urls":{"items":{"type":"string"},"type":"array","title":"Download Urls","description":"Signed download URLs for each output image (24-hour expiry). Populated when response_mode='urls', empty otherwise."},"known_subjects":{"$ref":"#/components/schemas/KnownGeneratedSubjectCounts","description":"Dictionary mapping a known subject's `profile_id` to the number of times they were generated. If multiple variations are generated, this will be the aggregated count across all variations."}},"type":"object","required":["known_subjects"],"title":"EditResponse","description":"Response returned by the edit, generate, and enhance endpoints."},"EnhanceRequest":{"properties":{"async":{"type":"boolean","title":"Async","description":"Submit asynchronously. Returns 202 with a job_id to poll at GET /v1/jobs/{job_id} instead of holding the connection until the image is ready.","default":false},"callback_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callback Url","description":"Optional https URL we POST the signed result to on completion. Best-effort (polling remains authoritative). Requires async=true."},"client_request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Request Id","description":"Idempotency key. A retried submit with the same value returns the original job instead of creating (and charging for) a new one."},"response_mode":{"type":"string","enum":["bytes","urls"],"title":"Response Mode","description":"Response delivery mode. 'bytes': return base64-encoded image data (default). 'urls': return signed download URLs (24-hour expiry) instead of image bytes.","default":"bytes"},"image":{"type":"string","title":"Image","description":"Input image as a raw base64 string or publicly accessible URL. Supported formats: JPEG, PNG, WebP, and HEIC/HEIF. Images larger than 4K (4096px) are automatically resized; for best results, provide images at 4K or below."},"profile_ids":{"items":{"type":"string"},"type":"array","title":"Profile Ids","description":"Subset of your account's profiles relevant to this request. Your account holds all profiles, but only pass the ones belonging to the end-user whose photo is being edited. These profiles serve as candidates for identity preservation. The fewer profiles you pass, the easier it is for our system to determine which profiles are relevant."},"num_output_images":{"type":"integer","maximum":4.0,"minimum":1.0,"title":"Num Output Images","description":"Number of output images to generate (1-4).","default":1},"output_format":{"type":"string","enum":["png","jpg"],"title":"Output Format","description":"Output image format (png, jpg). Default: png (will change to jpg on 2026-05-08).","default":"png"},"prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt","description":"Optional text instructions to guide the enhancement."}},"type":"object","required":["image"],"title":"EnhanceRequest","description":"Request body for the enhance endpoint.\n\nEnhances the provided input image without requiring a text prompt,\naspect ratio, or resolution -- those parameters are inferred automatically.\n\nThe image can be provided as a raw base64-encoded string or a publicly\naccessible URL. Both formats are accepted and detected automatically.","examples":[{"image":"https://example.com/photo.jpg","num_output_images":1,"profile_ids":["abc123"]}]},"GenerateRequest":{"properties":{"async":{"type":"boolean","title":"Async","description":"Submit asynchronously. Returns 202 with a job_id to poll at GET /v1/jobs/{job_id} instead of holding the connection until the image is ready.","default":false},"callback_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callback Url","description":"Optional https URL we POST the signed result to on completion. Best-effort (polling remains authoritative). Requires async=true."},"client_request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Request Id","description":"Idempotency key. A retried submit with the same value returns the original job instead of creating (and charging for) a new one."},"response_mode":{"type":"string","enum":["bytes","urls"],"title":"Response Mode","description":"Response delivery mode. 'bytes': return base64-encoded image data (default). 'urls': return signed download URLs (24-hour expiry) instead of image bytes.","default":"bytes"},"prompt":{"type":"string","title":"Prompt","description":"Text prompt describing the desired edit.","default":""},"num_output_images":{"type":"integer","maximum":4.0,"minimum":1.0,"title":"Num Output Images","description":"Number of output images to generate (1-4).","default":1},"aspect_ratio":{"type":"string","enum":["auto","1:1","3:4","4:3","9:16","16:9"],"title":"Aspect Ratio","description":"Output aspect ratio (auto, 1:1, 3:4, 4:3, 9:16, 16:9).","default":"auto"},"resolution":{"type":"string","enum":["1K","2K","4K"],"title":"Resolution","description":"Output resolution (1K, 2K, 4K). Per-model support varies; see /models.","default":"1K"},"quality":{"anyOf":[{"type":"string","enum":["auto","low","medium","high"]},{"type":"null"}],"title":"Quality","description":"Quality tier (auto, low, medium, high). Only supported by gpt-image-2; passing this field for other models returns 400. Omit (or pass 'auto') to let the provider pick — for gpt-image-2 this resolves to OpenAI's auto tier. Lower tiers reduce output tokens and therefore settled cost."},"base_model":{"type":"string","enum":["flux-2","gpt-image-2","nb2","qwen-image-2","reve"],"title":"Base Model","description":"Base model identifier. Omit to use the default base model. Unknown ids are rejected with 400; base models that do not support this endpoint's capability are also rejected with 400.","default":"nb2"},"output_format":{"type":"string","enum":["png","jpg"],"title":"Output Format","description":"Output image format (png, jpg). Default: png (will change to jpg on 2026-05-08).","default":"png"}},"type":"object","title":"GenerateRequest","description":"Request body for the generate endpoint.\n\nProvide a text prompt describing the image you want to create.\nReference profiles inline using ``[[profile_id]]`` syntax to\nspecify which people should appear in the generated image.","examples":[{"num_output_images":1,"prompt":"Generate a photo of [[abc123]] on a tropical beach"}]},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"JobError":{"properties":{"code":{"anyOf":[{"$ref":"#/components/schemas/PhotaErrorCode"},{"$ref":"#/components/schemas/EditJobErrorCode"}],"title":"Code","description":"Machine-readable error code (a PhotaErrorCode or job-infra EditJobErrorCode)."},"message":{"type":"string","title":"Message","description":"Human-readable error description."}},"type":"object","required":["code","message"],"title":"JobError","description":"Failure detail surfaced on a failed job.\n\n``code`` is one of the synchronous customer-facing categories\n(:class:`PhotaErrorCode`, so an async pipeline failure surfaces the same\nstable code as its sync twin) or a job-infrastructure-specific code\n(:class:`~database.models.EditJobErrorCode`, for the watchdog reap /\ndispatch-exhaustion states that have no synchronous equivalent)."},"JobHandle":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Opaque job identifier. Poll GET /v1/jobs/{job_id} for status."},"status":{"type":"string","const":"pending","title":"Status","description":"Always 'pending' at submit; transitions to running then succeeded/failed.","default":"pending"},"poll_url":{"type":"string","title":"Poll Url","description":"Relative path to poll for job status, e.g. '/v1/jobs/<job_id>'."}},"type":"object","required":["job_id","poll_url"],"title":"JobHandle","description":"202 response when an edit/generate/enhance/remix is submitted with async=true."},"JobStatus":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","enum":["pending","running","succeeded","failed"],"title":"Status"},"operation":{"type":"string","enum":["edit","generate","enhance","remix"],"title":"Operation"},"result":{"anyOf":[{"$ref":"#/components/schemas/EditResponse"},{"type":"null"}],"description":"The edit/generate result (download_urls populated) once status is 'succeeded'."},"error":{"anyOf":[{"$ref":"#/components/schemas/JobError"},{"type":"null"}],"description":"Populated once status is 'failed'."},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"}},"type":"object","required":["job_id","status","operation","created_at"],"title":"JobStatus","description":"Status of an async edit/generate job (GET /v1/jobs/{job_id})."},"KnownGeneratedSubjectCounts":{"properties":{"counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Counts","description":"Mapping of profile ID to the number of times that subject was generated."}},"type":"object","title":"KnownGeneratedSubjectCounts","description":"Counts of known subjects generated, keyed by profile ID."},"PhotaError400Response":{"properties":{"detail":{"type":"string","title":"Detail","description":"Human-readable error message."},"code":{"type":"string","enum":["CONTENT_MODERATION","INVALID_REQUEST"],"title":"Code","description":"Machine-readable error code."},"request_id":{"type":"string","title":"Request Id","description":"Unique request identifier for support."}},"type":"object","required":["detail","code","request_id"],"title":"PhotaError400Response","description":"Error response for 400 Bad Request."},"PhotaError404Response":{"properties":{"detail":{"type":"string","title":"Detail","description":"Human-readable error message."},"code":{"type":"string","enum":["NOT_FOUND","PROFILE_NOT_FOUND","PROFILE_NOT_READY"],"title":"Code","description":"Machine-readable error code."},"request_id":{"type":"string","title":"Request Id","description":"Unique request identifier for support."}},"type":"object","required":["detail","code","request_id"],"title":"PhotaError404Response","description":"Error response for 404 Not Found."},"PhotaError500Response":{"properties":{"detail":{"type":"string","title":"Detail","description":"Human-readable error message."},"code":{"type":"string","const":"INTERNAL_ERROR","title":"Code","description":"Machine-readable error code."},"request_id":{"type":"string","title":"Request Id","description":"Unique request identifier for support."}},"type":"object","required":["detail","code","request_id"],"title":"PhotaError500Response","description":"Error response for 500 Internal Server Error."},"PhotaError503Response":{"properties":{"detail":{"type":"string","title":"Detail","description":"Human-readable error message."},"code":{"type":"string","const":"SERVICE_UNAVAILABLE","title":"Code","description":"Machine-readable error code."},"request_id":{"type":"string","title":"Request Id","description":"Unique request identifier for support."}},"type":"object","required":["detail","code","request_id"],"title":"PhotaError503Response","description":"Error response for 503 Service Unavailable."},"PhotaErrorCode":{"type":"string","enum":["CONTENT_MODERATION","SERVICE_UNAVAILABLE","NOT_FOUND","PROFILE_NOT_FOUND","PROFILE_NOT_READY","INVALID_REQUEST","INTERNAL_ERROR"],"title":"PhotaErrorCode","description":"Machine-readable error codes returned by the Phota API.\n\nClients can use these codes to programmatically react to specific error\ntypes without parsing human-readable messages.\n\nAttributes:\n    CONTENT_MODERATION: Content was blocked by a safety filter.\n    SERVICE_UNAVAILABLE: Upstream resource exhaustion or rate limit.\n    NOT_FOUND: Generic resource not found or no longer available (unknown\n        route, unknown job, or an expired job result).\n    PROFILE_NOT_FOUND: The requested profile does not exist.\n    PROFILE_NOT_READY: The profile exists but training has not completed.\n    INVALID_REQUEST: Malformed input, missing fields, or bad parameters.\n    INTERNAL_ERROR: An unexpected internal error (genuine bug)."},"ProfileIdsResponse":{"properties":{"profiles":{"items":{"$ref":"#/components/schemas/ProfileInfo"},"type":"array","title":"Profiles","description":"List of profile summaries. Empty if the user has no profiles."}},"type":"object","required":["profiles"],"title":"ProfileIdsResponse","description":"Response containing all profiles owned by the authenticated user."},"ProfileInfo":{"properties":{"profile_id":{"type":"string","title":"Profile Id","description":"The profile identifier."},"status":{"$ref":"#/components/schemas/ProfileStatus","description":"Current status of the profile."},"tag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag","description":"Tag associated with this profile, if any."}},"type":"object","required":["profile_id","status"],"title":"ProfileInfo","description":"Summary information for a single profile."},"ProfileStatus":{"type":"string","enum":["VALIDATING","QUEUING","IN_PROGRESS","READY","ERROR","INACTIVE"],"title":"ProfileStatus","description":"Status of a profile."},"ProfileStatusResponse":{"properties":{"profile_id":{"type":"string","title":"Profile Id","description":"The queried profile identifier."},"status":{"$ref":"#/components/schemas/ProfileStatus","description":"Current status of the profile."},"message":{"type":"string","title":"Message","description":"Optional message in case of an error."}},"type":"object","required":["profile_id","status","message"],"title":"ProfileStatusResponse","description":"Response containing the current status of a profile."},"RemixRequest":{"properties":{"async":{"type":"boolean","title":"Async","description":"Submit asynchronously. Returns 202 with a job_id to poll at GET /v1/jobs/{job_id} instead of holding the connection until the image is ready.","default":false},"callback_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callback Url","description":"Optional https URL we POST the signed result to on completion. Best-effort (polling remains authoritative). Requires async=true."},"client_request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Request Id","description":"Idempotency key. A retried submit with the same value returns the original job instead of creating (and charging for) a new one."},"response_mode":{"type":"string","enum":["bytes","urls"],"title":"Response Mode","description":"Response delivery mode. 'bytes': return base64-encoded image data (default). 'urls': return signed download URLs (24-hour expiry) instead of image bytes.","default":"bytes"},"input_image":{"type":"string","title":"Input Image","description":"Input image as a raw base64 string or publicly accessible URL. Supported formats: JPEG, PNG, WebP, and HEIC/HEIF. Images larger than 4K (4096px) are automatically resized."},"reference_image":{"type":"string","title":"Reference Image","description":"Reference image as a raw base64 string or publicly accessible URL. The output keeps the input image's content rendered in this image's style."},"profile_ids":{"items":{"type":"string"},"type":"array","title":"Profile Ids","description":"Profile IDs for identity preservation in the output. When a listed profile is recognized in the input image, the person's likeness is preserved through the restyle. Optional.","default":[]},"prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt","description":"Optional text instructions to steer the remix (e.g. what to keep or emphasize)."},"num_output_images":{"type":"integer","maximum":4.0,"minimum":1.0,"title":"Num Output Images","description":"Number of output images to generate (1-4).","default":1},"output_format":{"type":"string","enum":["png","jpg"],"title":"Output Format","description":"Output image format (png, jpg). Default: png (will change to jpg on 2026-05-08).","default":"png"}},"type":"object","required":["input_image","reference_image"],"title":"RemixRequest","description":"Request body for the remix endpoint.","examples":[{"input_image":"https://example.com/photo.jpg","num_output_images":2,"output_format":"jpg","prompt":"Keep the original background","reference_image":"https://example.com/reference.jpg","response_mode":"urls"}]},"TrainingTier":{"type":"string","enum":["standard","fast"],"title":"TrainingTier","description":"Training tier for a profile job.\n\nAttributes:\n    STANDARD: Full-quality training (10-50 images, 500-1000 steps scaled by image count).\n    FAST: Reduced-image, faster-turnaround training (5-10 images, 150-300 steps scaled by image count)."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WebhookSecretResponse":{"properties":{"webhook_signing_secret":{"type":"string","title":"Webhook Signing Secret","description":"Per-developer HMAC key for verifying the X-Phota-Signature on job callbacks."}},"type":"object","required":["webhook_signing_secret"],"title":"WebhookSecretResponse","description":"The authenticated developer's webhook signing secret (GET .../webhook-secret).\n\nThe same per-developer secret used to HMAC-sign the ``X-Phota-Signature``\nheader on async job callbacks. Use it to verify a callback's authenticity:\nrecompute ``sha256`` HMAC over the exact received body and compare."}},"securitySchemes":{"APIKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key"}}},"tags":[{"name":"Studio","description":"Image editing, generation, and enhancement."},{"name":"Profiles","description":"Profile lifecycle: create, query, delete."}],"servers":[{"url":"https://api.photalabs.com"}]}