{"openapi":"3.1.0","info":{"title":"OPS/iO Portrait API","version":"1.0.0","description":"AI portrait generation API — build a Personal AI Asset (PAA) from source photos, then generate studio-quality portraits in any registered style. Pure pay-as-you-go: ¥1,100 per profile build, ¥300 per generated image."},"servers":[{"url":"https://io.order-photo.com/api"}],"security":[{"apiKey":[]}],"paths":{"/v1/profiles":{"post":{"operationId":"postV1Profiles","summary":"Create a profile (PAA)","description":"Builds a Personal AI Asset (PAA) from source photos. Provide photos as publicly accessible HTTPS URLs; the server fetches and stores them. Building is asynchronous — the profile's status becomes 'ready' on completion, and a 'profile.ready' webhook is delivered. face_only mode requires 4 face angles (face_front / face_front_smile / face_left45 / face_right45); full mode requires all 12 angles. The ¥1,100 charge applies only when the build succeeds.\n\nソース写真からパーソナル AI アセット（PAA）を構築します。写真は公開 HTTPS URL で渡し、サーバー側で取得・保存します。構築は非同期で、完了すると status が ready になり、Webhook（profile.ready）でも通知されます。face_only モードは顔 4 アングル（face_front / face_front_smile / face_left45 / face_right45）、full モードは 12 アングルすべてが必要です。課金（¥1,100）は構築成功時のみ発生します。","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"id":"b6a4f6de-0000-0000-0000-000000000001","status":"pending","display_name":"Sato Hanako","capture_mode":"face_only","progress_pct":0,"created_at":"2026-08-12T00:00:00Z","updated_at":"2026-08-12T00:00:00Z"}}}},"400":{"description":"invalid_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden_scope / api_disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"photo_fetch_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"enqueue_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"display_name":{"type":"string","minLength":1,"maxLength":30,"description":"Profile display name (≤30 chars) / プロファイル表示名（30文字以内）"},"capture_mode":{"type":"string","enum":["full","face_only"],"default":"face_only","description":"face_only = 4 face angles, full = all 12 angles / face_only は顔4アングル、full は12アングル必須"},"photos":{"type":"array","items":{"type":"object","properties":{"angle":{"type":"string","enum":["front","left45","right45","back","face_front","face_front_smile","face_front_up","face_left45","face_right45","face_left90","face_right90","face_oblique"],"description":"Photo angle / 写真のアングル"},"url":{"type":"string","format":"uri","description":"Publicly accessible HTTPS URL / 公開 HTTPS URL"}},"required":["angle","url"],"additionalProperties":false},"minItems":1,"maxItems":12,"description":"Source photos, one per required angle / 必須アングルぶんのソース写真"}},"required":["display_name","photos"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"example":{"display_name":"Sato Hanako","capture_mode":"face_only","photos":[{"angle":"face_front","url":"https://example.com/photos/front.jpg"},{"angle":"face_front_smile","url":"https://example.com/photos/smile.jpg"},{"angle":"face_left45","url":"https://example.com/photos/left45.jpg"},{"angle":"face_right45","url":"https://example.com/photos/right45.jpg"}]}}}}},"get":{"operationId":"getV1Profiles","summary":"List profiles","description":"Returns this organization's API-created profiles, newest first.\n\nこの組織の API 経由で作成されたプロファイルを新しい順に返します。","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"data":[{"id":"b6a4f6de-0000-0000-0000-000000000001","status":"ready","display_name":"Sato Hanako","capture_mode":"face_only","created_at":"2026-08-12T00:00:00Z"}],"has_more":false}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":20,"description":"Page size (1–100, default 20) / 取得件数"}},{"name":"before","in":"query","required":false,"schema":{"type":"string","format":"date-time","description":"Cursor: return items created before this ISO datetime / カーソル（ISO 日時）"}}]}},"/v1/profiles/{id}":{"get":{"operationId":"getV1ProfilesById","summary":"Retrieve a profile","description":"Polls the build status of a profile. status transitions: pending → generating → ready / failed.\n\nプロファイルの構築状態をポーリングします。status: pending → generating → ready / failed。","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"id":"b6a4f6de-0000-0000-0000-000000000001","status":"ready","display_name":"Sato Hanako","capture_mode":"face_only","progress_pct":100,"created_at":"2026-08-12T00:00:00Z","updated_at":"2026-08-12T00:05:00Z"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]},"delete":{"operationId":"deleteV1ProfilesById","summary":"Delete a profile","description":"Deletes the profile together with all its generated images and source photos. This cannot be undone.\n\nプロファイルと、そこから生成された画像・ソース写真をすべて削除します。取り消せません。","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"deleted":true}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/v1/generations":{"post":{"operationId":"postV1Generations","summary":"Create a generation","description":"Generates portraits from a 'ready' profile. Processing is asynchronous — poll GET /v1/generations/{id} or subscribe to the 'generation.completed' webhook. The ¥300/image charge applies only to completed images. A generation takes 2–3 minutes on average.\n\nready 状態のプロファイルからポートレートを生成します。非同期処理で、完了は GET /v1/generations/{id} のポーリングか Webhook（generation.completed）で受け取ります。課金（¥300/枚）は完了した枚数分のみ発生します。1 生成あたり平均 2〜3 分かかります。","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"id":"e2d81f77-0000-0000-0000-000000000002","status":"processing","profile_id":"b6a4f6de-0000-0000-0000-000000000001","count":1,"created_at":"2026-08-12T00:10:00Z"}}}},"400":{"description":"invalid_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"hard_cap_reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden_scope / api_disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"profile_not_ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"enqueue_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid","description":"Profile id with status=ready / ready 状態のプロファイル id"},"style_preset_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Style preset id from GET /v1/styles / スタイルプリセット id"},"scene_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Scene id (optional) / シーン id（任意）"},"framing":{"anyOf":[{"type":"string","enum":["full","knee-up","half","bust-up","face-up"]},{"type":"null"}],"description":"Framing. face_only profiles allow bust-up / face-up only / 画角。face_only は bust-up / face-up のみ"},"aspect_ratio":{"anyOf":[{"type":"string","enum":["1:1","3:4","4:5","9:16","4:3","3:2","16:9"]},{"type":"null"}],"description":"Aspect ratio / アスペクト比"},"count":{"type":"integer","minimum":1,"maximum":4,"default":1,"description":"Number of images per request (1–4) / 生成枚数（1〜4）"},"quality":{"type":"string","enum":["medium","high"],"default":"high","description":"high = 2K resolution / high は 2K 解像度"}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"example":{"profile_id":"b6a4f6de-0000-0000-0000-000000000001","style_preset_id":"a1f0c9aa-0000-0000-0000-000000000003","framing":"bust-up","aspect_ratio":"3:4","count":1,"quality":"high"}}}}},"get":{"operationId":"getV1Generations","summary":"List generations","description":"Returns this organization's API-originated generations, newest first.\n\nこの組織の API 経由の生成を新しい順に返します。","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"data":[{"id":"e2d81f77-0000-0000-0000-000000000002","status":"completed","profile_id":"b6a4f6de-0000-0000-0000-000000000001","images":[{"url":"https://…（1時間有効の署名URL）","expires_in":3600}],"created_at":"2026-08-12T00:10:00Z","completed_at":"2026-08-12T00:12:30Z"}],"has_more":false}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":20,"description":"Page size (1–100, default 20) / 取得件数"}},{"name":"before","in":"query","required":false,"schema":{"type":"string","format":"date-time","description":"Cursor: return items created before this ISO datetime / カーソル（ISO 日時）"}}]}},"/v1/generations/{id}":{"get":{"operationId":"getV1GenerationsById","summary":"Retrieve a generation","description":"Polls a generation. Once 'completed', the images array contains signed URLs valid for 1 hour. Re-fetch this endpoint to obtain fresh URLs after expiry.\n\n生成の状態をポーリングします。completed になると images に署名付き URL（1 時間有効）が入ります。URL の期限が切れたら同じエンドポイントを再取得してください。","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"id":"e2d81f77-0000-0000-0000-000000000002","status":"completed","profile_id":"b6a4f6de-0000-0000-0000-000000000001","progress_pct":100,"images":[{"url":"https://…（1時間有効の署名URL）","expires_in":3600}],"created_at":"2026-08-12T00:10:00Z","completed_at":"2026-08-12T00:12:30Z"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/v1/styles":{"get":{"operationId":"getV1Styles","summary":"List styles","description":"Returns the catalog of style presets available for generation. Pass a style's id as style_preset_id in POST /v1/generations.\n\n生成に指定できるスタイルプリセットの一覧を返します。各スタイルの id を POST /v1/generations の style_preset_id に渡します。","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"data":[{"id":"a1f0c9aa-0000-0000-0000-000000000003","name":"ビジネス・スタジオ","category":"business","follow_pose":false}]}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/usage":{"get":{"operationId":"getV1Usage","summary":"Retrieve current usage","description":"Returns this month's API usage (images consumed, remaining hard cap) and the estimated charges.\n\n当月の API 使用量（生成消費枚数・hard cap 残量）と概算金額を返します。","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"period_images_used":42,"period_profiles_built":3,"hard_cap_images":1000,"hard_cap_remaining":958,"estimated_charges_jpy":15900,"unit_prices":{"profile_jpy":1100,"image_jpy":300}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhook-endpoints":{"post":{"operationId":"postV1WebhookEndpoints","summary":"Create a webhook endpoint","description":"Registers a delivery URL for events. The returned secret (whsec_...) is used for signature verification — it is shown only once and cannot be retrieved later. Deliveries are signed via the X-Opsio-Signature header (HMAC-SHA256).\n\nイベント配信先 URL を登録します。レスポンスの secret（whsec_...）は署名検証に使います — この時だけ平文で返され、以後取得できません。配信は X-Opsio-Signature ヘッダ（HMAC-SHA256）で署名されます。","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"id":"c3b2a1d0-0000-0000-0000-000000000004","url":"https://example.com/webhooks/opsio","events":["generation.completed","generation.failed"],"secret":"whsec_（この時だけ表示）","active":true}}}},"400":{"description":"invalid_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Delivery URL (HTTPS only) / 配信先 URL（HTTPS のみ）"},"events":{"type":"array","items":{"type":"string","enum":["profile.ready","profile.failed","generation.completed","generation.failed"]},"minItems":1,"default":["profile.ready","profile.failed","generation.completed","generation.failed"],"description":"Subscribed events (default: all) / 購読イベント（省略時は全イベント）"}},"required":["url"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"example":{"url":"https://example.com/webhooks/opsio","events":["generation.completed","generation.failed"]}}}}},"get":{"operationId":"getV1WebhookEndpoints","summary":"List webhook endpoints","description":"Returns registered endpoints. Secrets are not included.\n\n登録済みの配信先を返します。secret は含まれません。","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"data":[{"id":"c3b2a1d0-0000-0000-0000-000000000004","url":"https://example.com/webhooks/opsio","events":["generation.completed","generation.failed"],"active":true}]}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhook-endpoints/{id}":{"delete":{"operationId":"deleteV1WebhookEndpointsById","summary":"Delete a webhook endpoint","description":"Deletes the endpoint. No further deliveries will be made to this URL.\n\n配信先を削除します。以後この URL への配信は行われません。","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"deleted":true}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/v1/openapi.json":{"get":{"operationId":"getV1OpenapiJson","summary":"OpenAPI 3.1 document","description":"Returns the OpenAPI 3.1 document for this API. No authentication required. The document is generated from the registry at request time and is therefore always current.\n\nこの API の OpenAPI 3.1 仕様を返します。認証不要。仕様はレジストリからリクエスト時に生成されるため常に最新です。","security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"openapi":"3.1.0","info":{"title":"OPS/iO Portrait API"}}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"ops_sk_...","description":"Authorization: Bearer ops_sk_..."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"doc_url":{"type":"string","format":"uri"}},"required":["code","message","doc_url"]}},"required":["error"]}}}}