{
  "openapi": "3.1.0",
  "info": {
    "title": "AI Photos of You Studio API",
    "version": "1.0.0",
    "description": "The session-authenticated API used by AI Photos of You. Discovery does not grant access or permission to generate photos, purchase credits, or delete data. Mutations require an Origin header matching the application origin. Authentication routes under /api/auth/ are managed by Better Auth; /api/webhooks/polar is a signed provider callback, not a customer API. API-Version: 1 selects the current major version; omission defaults to 1. Breaking contract changes require a new major version. Additive fields may be introduced; clients should tolerate unknown fields and error codes. Planned retirement will be announced in these docs and through RFC 9745 Deprecation and RFC 8594 Sunset response headers when dates are scheduled. No retirement is currently scheduled. This policy covers documented Studio endpoints, not Better Auth or Polar callbacks."
  },
  "servers": [
    {
      "url": "/api"
    }
  ],
  "externalDocs": {
    "description": "Studio API documentation",
    "url": "/studio-api"
  },
  "security": [
    {
      "sessionCookie": []
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "summary": "Check service liveness",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "const": "ok",
                      "type": "string"
                    },
                    "service": {
                      "type": "string"
                    }
                  },
                  "required": ["status", "service"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Liveness only; does not verify billing, generation, email, or output quality.",
        "security": [],
        "operationId": "getHealth",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ]
      }
    },
    "/config": {
      "get": {
        "summary": "Read public integration availability and credit packages",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "google": {
                      "type": "boolean"
                    },
                    "email": {
                      "type": "boolean"
                    },
                    "generation": {
                      "type": "boolean"
                    },
                    "billing": {
                      "type": "boolean"
                    },
                    "googleAdsConversionId": {
                      "type": "string"
                    },
                    "googleAdsConversionLabel": {
                      "type": "string"
                    },
                    "posthogKey": {
                      "type": "string"
                    },
                    "posthogHost": {
                      "type": "string"
                    },
                    "supportEmail": {
                      "type": "string"
                    },
                    "packages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CreditPackage"
                      }
                    }
                  },
                  "required": [
                    "google",
                    "email",
                    "generation",
                    "billing",
                    "googleAdsConversionId",
                    "googleAdsConversionLabel",
                    "posthogKey",
                    "posthogHost",
                    "supportEmail",
                    "packages"
                  ]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [],
        "operationId": "getPublicConfig",
        "description": "Read public integration availability and credit packages.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ]
      }
    },
    "/me": {
      "get": {
        "summary": "Read the current session and credit balance",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "null"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "user": {
                          "$ref": "#/components/schemas/SessionUser"
                        },
                        "credits": {
                          "type": "integer"
                        },
                        "paymentProcessing": {
                          "type": "boolean"
                        }
                      },
                      "required": ["user", "credits", "paymentProcessing"]
                    }
                  ]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Returns null when signed out or authentication is unavailable. A session cookie is optional.",
        "security": [
          {},
          {
            "sessionCookie": []
          }
        ],
        "operationId": "getCurrentSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ]
      }
    },
    "/subjects": {
      "get": {
        "summary": "Get your saved reference set",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Subject"
                  }
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Returns an empty array or the oldest subject record for your account. photo_count and cover_id include saved reference photos across all legacy subjects; existing IDs remain valid.",
        "operationId": "listSubjects",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ]
      },
      "post": {
        "summary": "Get or create your saved reference set",
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    }
                  },
                  "required": ["id"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "200": {
            "description": "Existing saved reference set",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    }
                  },
                  "required": ["id"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60
                  }
                },
                "required": ["name"]
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "operationId": "createSubject",
        "description": "Returns the existing oldest subject with 200, or creates the first subject with 201. Concurrent requests reuse the same subject. The name is used only on first creation."
      }
    },
    "/subjects/{id}": {
      "delete": {
        "summary": "Delete a profile and its reference photos",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "const": true,
                      "type": "boolean"
                    }
                  },
                  "required": ["deleted"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Delete shoots using this profile first; otherwise returns 409.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "operationId": "deleteSubject"
      },
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Owned resource identifier."
        }
      ]
    },
    "/uploads": {
      "post": {
        "summary": "Upload a reference photo",
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    }
                  },
                  "required": ["id"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "description": "JPEG, PNG or WebP, nonempty and at most 2 MiB per file; at most five references per account, including legacy subjects. Ownership and file signatures are checked.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "subjectId": {
                    "type": "string"
                  },
                  "file": {
                    "description": "Raw JPEG, PNG, or WebP file bytes, at most 2 MiB."
                  }
                },
                "required": ["subjectId", "file"]
              },
              "encoding": {
                "file": {
                  "contentType": "image/jpeg, image/png, image/webp"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "operationId": "uploadReferencePhoto"
      }
    },
    "/photos": {
      "get": {
        "summary": "List your reference and generated photos",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PhotoPage"
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Items include id, job_id, subject_id, mime, favorite, created_at, profile_id, pack_id, prompt and ratio. Some fields are null for reference photos.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 100
            },
            "description": "Maximum items per page."
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque nextCursor from the previous page.",
            "schema": {
              "type": "string",
              "maxLength": 2048
            }
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "operationId": "listPhotos"
      }
    },
    "/photos/{id}/file": {
      "get": {
        "summary": "Read or download a private photo",
        "responses": {
          "200": {
            "description": "Private image bytes, with inline or attachment Content-Disposition.",
            "content": {
              "image/jpeg": {},
              "image/png": {},
              "image/webp": {}
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "parameters": [
          {
            "name": "download",
            "in": "query",
            "description": "Presence of this parameter requests an attachment.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "operationId": "getPhotoFile",
        "description": "Read or download a private photo."
      },
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Owned resource identifier."
        }
      ]
    },
    "/photos/{id}": {
      "patch": {
        "summary": "Set a photo favorite",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "saved": {
                      "const": true,
                      "type": "boolean"
                    }
                  },
                  "required": ["saved"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "favorite": {
                    "type": "boolean"
                  }
                },
                "required": ["favorite"]
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "operationId": "setPhotoFavorite",
        "description": "Set a photo favorite."
      },
      "delete": {
        "summary": "Delete a photo",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "const": true,
                      "type": "boolean"
                    }
                  },
                  "required": ["deleted"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Returns 409 if an active shoot needs this photo or its reference profile.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "operationId": "deletePhoto"
      },
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Owned resource identifier."
        }
      ]
    },
    "/shoots": {
      "get": {
        "summary": "List your shoots and progress",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShootPage"
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Items contain job records and completed output counts. Poll this endpoint for progress and /photos for available outputs.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 100
            },
            "description": "Maximum items per page."
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque nextCursor from the previous page.",
            "schema": {
              "type": "string",
              "maxLength": 2048
            }
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "operationId": "listShoots"
      },
      "post": {
        "summary": "Create a paid shoot",
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    }
                  },
                  "required": ["id"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "200": {
            "description": "Existing shoot",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    }
                  },
                  "required": ["id"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Reserves two credits per output and requires an owned subject and account reference photos and explicit consent. Use a fresh UUID for a new shoot and the same UUID for retries. A retry for an existing owned job returns its id with 200. Custom generation/edit requires a nonblank prompt; edit/upscale requires an owned generated sourceId. Pack IDs other than custom must exist in the Studio catalog. At most two active shoots/batches per account. Acceptance does not mean output generation is complete or quality approved. Generation uses saved references across all legacy subjects. Accounts with more than five legacy references must remove photos before starting a new generation.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ShootInput"
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "operationId": "createShoot"
      }
    },
    "/shoots/batch": {
      "post": {
        "summary": "Create a paid batch across styles",
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ids": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": ["ids"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "200": {
            "description": "Previously accepted batch",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ids": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": ["ids"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Same credit, consent and reference requirements as a shoot. Choose each style or curated pack shot ID once, with at most 48 total outputs. Optional item prompts apply to that shot before the shared prompt. Custom style requires a nonblank shared or item prompt. Reuse the same UUID and selection to retry; a changed selection with that UUID returns 409.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchInput"
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "operationId": "createShootBatch"
      }
    },
    "/shoots/{id}": {
      "delete": {
        "summary": "Delete a finished shoot and its photos",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "const": true,
                      "type": "boolean"
                    }
                  },
                  "required": ["deleted"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Returns 409 for unavailable or active shoots, or when an active shoot depends on its photos.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "operationId": "deleteShoot"
      },
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Owned resource identifier."
        }
      ]
    },
    "/checkout": {
      "post": {
        "summary": "Create a credit purchase checkout",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  "required": ["url"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "description": "Returns a hosted payment URL. A redirect is not proof of payment; credits depend on verified payment reconciliation.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "packageId": {
                    "type": "string",
                    "enum": ["starter", "studio", "pro"]
                  }
                },
                "required": ["packageId"]
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "operationId": "createCheckout"
      }
    },
    "/checkout/{id}": {
      "get": {
        "summary": "Read purchase status and current credits",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": ["pending", "paid", "refunded"]
                    },
                    "credits": {
                      "type": "integer"
                    },
                    "conversion": {
                      "oneOf": [
                        {
                          "type": "null"
                        },
                        {
                          "type": "object",
                          "properties": {
                            "transactionId": {
                              "type": "string"
                            },
                            "value": {
                              "type": "number"
                            },
                            "currency": {
                              "type": "string"
                            }
                          },
                          "required": ["transactionId", "value", "currency"]
                        }
                      ]
                    }
                  },
                  "required": ["status", "credits", "conversion"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "The id is the checkout_id from the return URL. A checkout without a matching owned purchase returns pending, including unknown checkout IDs.",
        "operationId": "getCheckoutStatus",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ]
      },
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Owned resource identifier."
        }
      ]
    },
    "/billing": {
      "post": {
        "summary": "Open the billing portal",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  "required": ["url"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "operationId": "createBillingPortal",
        "description": "Open the billing portal."
      }
    },
    "/credits": {
      "get": {
        "summary": "Read your latest 100 credit ledger entries",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "delta": {
                        "type": "integer"
                      },
                      "reason": {
                        "type": "string"
                      },
                      "created_at": {
                        "type": "integer"
                      }
                    },
                    "required": ["delta", "reason", "created_at"]
                  }
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "operationId": "listCreditEntries",
        "description": "Read your latest 100 credit ledger entries.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ]
      }
    },
    "/attribution": {
      "post": {
        "summary": "Save first account attribution",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "saved": {
                      "const": true,
                      "type": "boolean"
                    }
                  },
                  "required": ["saved"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Stores only the first attribution. Organic attribution requires a recognized organic source, a supported landing-page campaign and content organic-search. Submit only with the user\u2019s measurement consent.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "source": {
                    "type": "string",
                    "maxLength": 120,
                    "default": ""
                  },
                  "campaign": {
                    "type": "string",
                    "maxLength": 120,
                    "default": ""
                  },
                  "content": {
                    "type": "string",
                    "maxLength": 120,
                    "default": ""
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "operationId": "saveAttribution"
      }
    },
    "/account": {
      "delete": {
        "summary": "Delete your account and media",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "const": true,
                      "type": "boolean"
                    }
                  },
                  "required": ["deleted"]
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "4XX": {
            "$ref": "#/components/responses/Error"
          },
          "5XX": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Destructive. Requires no queued or running shoots; otherwise returns 409. Removes authentication and media records while retaining the financial ledger required for reconciliation.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "confirmation": {
                    "const": "DELETE",
                    "type": "string"
                  }
                },
                "required": ["confirmation"]
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "operationId": "deleteAccount"
      }
    }
  },
  "components": {
    "securitySchemes": {
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__Secure-better-auth.session_token",
        "description": "Sign in through the application. HTTPS uses the secure Better Auth session cookie; local HTTP uses better-auth.session_token. There are no API keys or bearer tokens."
      }
    },
    "responses": {
      "Error": {
        "description": "Error response. Common statuses: 400 invalid input; 401 sign-in required; 403 invalid Origin; 404 missing or unowned resource; 409 credits, limits, active work or deletion conflict; 413 oversized upload; 429 rate limit; 500 internal failure; 503 integration unavailable.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        },
        "headers": {
          "API-Version": {
            "$ref": "#/components/headers/ApiVersion"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          }
        }
      },
      "RateLimited": {
        "description": "Per-account quota exhausted. Retry after the indicated delay.",
        "headers": {
          "API-Version": {
            "$ref": "#/components/headers/ApiVersion"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      }
    },
    "schemas": {
      "ShootInput": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "subjectId": {
            "type": "string",
            "minLength": 1
          },
          "consent": {
            "const": true,
            "type": "boolean"
          },
          "packId": {
            "type": "string",
            "maxLength": 50,
            "default": "custom"
          },
          "prompt": {
            "type": "string",
            "maxLength": 4002,
            "default": ""
          },
          "count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12
          },
          "ratio": {
            "type": "string",
            "enum": ["2:3", "1:1", "3:2"]
          },
          "mode": {
            "type": "string",
            "enum": ["generate", "edit", "upscale"],
            "default": "generate"
          },
          "sourceId": {
            "type": ["string", "null"],
            "default": null
          },
          "notify": {
            "type": "boolean",
            "default": false
          }
        },
        "required": ["id", "subjectId", "consent", "count", "ratio"]
      },
      "BatchInput": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "subjectId": {
            "type": "string",
            "minLength": 1
          },
          "consent": {
            "const": true,
            "type": "boolean"
          },
          "prompt": {
            "type": "string",
            "maxLength": 2000,
            "default": ""
          },
          "ratio": {
            "type": "string",
            "enum": ["2:3", "1:1", "3:2"]
          },
          "notify": {
            "type": "boolean",
            "default": false
          },
          "items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 48,
            "items": {
              "type": "object",
              "properties": {
                "packId": {
                  "type": "string",
                  "maxLength": 50,
                  "description": "Style preset ID, curated pack shot ID, or custom."
                },
                "prompt": {
                  "type": "string",
                  "maxLength": 2000,
                  "description": "Optional directions for this item, followed by the shared batch prompt."
                },
                "count": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 12
                }
              },
              "required": ["packId", "count"]
            }
          }
        },
        "required": ["id", "subjectId", "consent", "items", "ratio"]
      },
      "ApiError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable explanation; do not branch on this text."
          },
          "code": {
            "type": "string",
            "description": "Machine-readable category. Handle unknown codes using the HTTP status.",
            "examples": [
              "bad_request",
              "unauthenticated",
              "forbidden",
              "not_found",
              "conflict",
              "payload_too_large",
              "rate_limited",
              "internal_error",
              "unavailable",
              "unsupported_api_version"
            ]
          }
        },
        "required": ["error", "code"]
      },
      "CreditPackage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "credits": {
            "type": "integer"
          },
          "priceUsd": {
            "type": "number"
          }
        },
        "required": ["id", "name", "credits", "priceUsd"]
      },
      "SessionUser": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "emailVerified": {
            "type": "boolean"
          },
          "image": {
            "type": ["string", "null"]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": ["id", "name", "email", "emailVerified", "createdAt", "updatedAt"]
      },
      "Subject": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "user_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "created_at": {
            "type": "integer",
            "description": "Unix time in milliseconds."
          },
          "photo_count": {
            "type": "integer"
          },
          "cover_id": {
            "type": ["string", "null"]
          }
        },
        "required": ["id", "user_id", "name", "created_at", "photo_count", "cover_id"]
      },
      "Photo": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "job_id": {
            "type": ["string", "null"]
          },
          "subject_id": {
            "type": ["string", "null"]
          },
          "mime": {
            "type": "string"
          },
          "favorite": {
            "type": "integer",
            "enum": [0, 1]
          },
          "created_at": {
            "type": "integer",
            "description": "Unix time in milliseconds."
          },
          "profile_id": {
            "type": ["string", "null"]
          },
          "pack_id": {
            "type": ["string", "null"]
          },
          "prompt": {
            "type": ["string", "null"]
          },
          "ratio": {
            "type": ["string", "null"]
          }
        },
        "required": [
          "id",
          "job_id",
          "subject_id",
          "mime",
          "favorite",
          "created_at",
          "profile_id",
          "pack_id",
          "prompt",
          "ratio"
        ]
      },
      "Shoot": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "user_id": {
            "type": "string"
          },
          "subject_id": {
            "type": "string"
          },
          "pack_id": {
            "type": "string"
          },
          "prompt": {
            "type": "string"
          },
          "resolved_prompt": {
            "type": "string"
          },
          "ratio": {
            "type": "string"
          },
          "mode": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "source_id": {
            "type": ["string", "null"]
          },
          "batch_id": {
            "type": ["string", "null"]
          },
          "count": {
            "type": "integer"
          },
          "notify": {
            "type": "integer",
            "enum": [0, 1]
          },
          "created_at": {
            "type": "integer",
            "description": "Unix time in milliseconds."
          },
          "completed": {
            "type": "integer"
          },
          "last_reconciled_at": {
            "type": "integer",
            "description": "Unix time in milliseconds of the last reconciliation attempt; zero if never reconciled."
          }
        },
        "required": [
          "id",
          "user_id",
          "subject_id",
          "pack_id",
          "prompt",
          "resolved_prompt",
          "ratio",
          "mode",
          "model",
          "status",
          "source_id",
          "batch_id",
          "count",
          "notify",
          "created_at",
          "completed"
        ]
      },
      "PhotoPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Photo"
            }
          },
          "nextCursor": {
            "type": ["string", "null"]
          }
        },
        "required": ["items", "nextCursor"]
      },
      "ShootPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Shoot"
            }
          },
          "nextCursor": {
            "type": ["string", "null"]
          }
        },
        "required": ["items", "nextCursor"]
      }
    },
    "parameters": {
      "Origin": {
        "name": "Origin",
        "in": "header",
        "required": true,
        "description": "The application origin (scheme and host, plus port when non-default), without a trailing slash; must match the origin serving this API.",
        "schema": {
          "type": "string",
          "format": "uri"
        }
      },
      "ApiVersion": {
        "name": "API-Version",
        "in": "header",
        "required": false,
        "description": "Select API major version. Omission selects 1. Unsupported versions return 400 with code unsupported_api_version.",
        "schema": {
          "type": "string",
          "enum": ["1"],
          "default": "1"
        }
      }
    },
    "headers": {
      "ApiVersion": {
        "description": "API major version serving this response.",
        "schema": {
          "type": "string",
          "enum": ["1"]
        }
      },
      "RateLimit": {
        "description": "Quota remaining and seconds until reset, using draft-ietf-httpapi-ratelimit-headers-11 Structured Fields. Present after an account quota is checked.",
        "schema": {
          "type": "string"
        },
        "example": "\"profile\";r=119;t=3600"
      },
      "RateLimitPolicy": {
        "description": "Enforced per-account policy: q requests per w seconds. draft-ietf-httpapi-ratelimit-headers-11; not a published RFC.",
        "schema": {
          "type": "string"
        },
        "example": "\"profile\";q=120;w=3600"
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying a rate-limited request.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      }
    }
  }
}
