{
  "openapi": "3.1.0",
  "info": {
    "title": "AutoHustle API",
    "version": "1.0.0",
    "summary": "Put a face into any public Reel or TikTok, from a link.",
    "description": "Paste a public Instagram Reel or TikTok URL and get back a vertical MP4 with your avatar in it.\n\nHow it works: `POST /v1/analyses` reads the source and quotes a price — free, no credits spent.\n`POST /v1/generations` starts the job and debits prepaid credits. A run takes roughly 25 minutes,\nso poll `GET /v1/generations/{id}` until `status` is `completed` and read `video_url`.\n\nCredits are refunded automatically when a generation fails on our side. Nothing is ever published\non anyone's behalf — the API returns a file, and posting it stays a human decision.",
    "contact": {
      "name": "AutoHustle",
      "url": "https://autohustle.me/for-agents"
    },
    "termsOfService": "https://autohustle.me/terms"
  },
  "servers": [
    {
      "url": "https://autohustle.me",
      "description": "Production"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "analyses",
      "description": "Free source inspection and price quotes."
    },
    {
      "name": "generations",
      "description": "Paid video generation and polling."
    },
    {
      "name": "account",
      "description": "Balance, avatars, price list."
    }
  ],
  "paths": {
    "/api/v1/analyses": {
      "post": {
        "tags": [
          "analyses"
        ],
        "operationId": "createAnalysis",
        "summary": "Analyze a Reel and quote the price",
        "description": "Free. Returns the pipeline the source resolved to, the scene breakdown and the exact credit cost a generation would debit. Rate limited per key and per IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAnalysisRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Analysis and price quote.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalysisResponse"
                }
              }
            }
          },
          "400": {
            "description": "Body failed validation.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or consent has not been accepted.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "The source cannot be used — `reason` carries the pipeline reject code.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached. `Retry-After` says when to come back.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure. Safe to retry.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generations": {
      "post": {
        "tags": [
          "generations"
        ],
        "operationId": "createGeneration",
        "summary": "Start a video generation",
        "description": "Debits credits and queues the run. Returns immediately with an id to poll. Returns 402 with `required_credits` and `topup_url` when the workspace cannot cover it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateGenerationRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued. Poll `poll_url`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerationCreated"
                }
              }
            }
          },
          "400": {
            "description": "Body failed validation.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Not enough credits. Body carries `required_credits` and `topup_url`.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or consent has not been accepted.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Source rejected, avatar not ready, or pipeline unsupported.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached. `Retry-After` says when to come back.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure. Safe to retry.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generations/{id}": {
      "get": {
        "tags": [
          "generations"
        ],
        "operationId": "getGeneration",
        "summary": "Poll a generation",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Id returned by POST /api/v1/generations."
          }
        ],
        "responses": {
          "200": {
            "description": "Current state. `video_url` is set once `status` is `completed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerationStatus"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or consent has not been accepted.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No generation with that id in this workspace.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached. `Retry-After` says when to come back.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure. Safe to retry.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/pricing": {
      "get": {
        "tags": [
          "account"
        ],
        "operationId": "getPricing",
        "summary": "Machine-readable price list",
        "description": "Public — no API key required.",
        "security": [],
        "responses": {
          "200": {
            "description": "Credit costs per model, credit packs and subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PricingResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/avatars": {
      "get": {
        "tags": [
          "account"
        ],
        "operationId": "listAvatars",
        "summary": "List usable avatars",
        "responses": {
          "200": {
            "description": "Avatars in this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AvatarListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or consent has not been accepted.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached. `Retry-After` says when to come back.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure. Safe to retry.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/me": {
      "get": {
        "tags": [
          "account"
        ],
        "operationId": "getMe",
        "summary": "Key identity and credit balance",
        "responses": {
          "200": {
            "description": "Workspace, balance and granted scopes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or consent has not been accepted.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached. `Retry-After` says when to come back.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure. Safe to retry.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "An AutoHustle API key (`ah_live_…`), created at https://autohustle.me/settings. Send it as `Authorization: Bearer <key>`."
      }
    },
    "schemas": {
      "CreateAnalysisRequest": {
        "type": "object",
        "properties": {
          "source_url": {
            "type": "string",
            "maxLength": 2048,
            "format": "uri",
            "description": "Public Instagram Reel or TikTok video URL. Instagram photo posts and TikTok photo carousels are rejected.",
            "example": "https://www.instagram.com/reel/CxAmpLe123/"
          },
          "avatar_id": {
            "description": "Avatar to price the estimate against. Defaults to the last one used.",
            "type": "string"
          }
        },
        "required": [
          "source_url"
        ]
      },
      "AnalysisResponse": {
        "type": "object",
        "properties": {
          "storyboard_id": {
            "type": "string"
          },
          "post_id": {
            "type": "string"
          },
          "pipeline_route": {
            "type": "string",
            "description": "Which pipeline the source resolved to: A_HAPPYHORSE (face replace), A_T2V_NOSWAP, B_KLING (motion copy), C_NANO_BANANA (not generatable yet)."
          },
          "generatable": {
            "type": "boolean",
            "description": "False when the source was rejected — `reject_reason` says why."
          },
          "reject_reason": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "scenes": {
            "type": "array",
            "items": {
              "$ref": "#/$defs/AnalysisScene"
            }
          },
          "classification": {
            "type": "object",
            "properties": {
              "content_type": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "hook_type": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "face_visible": {
                "anyOf": [
                  {
                    "type": "boolean"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "motion_intensity": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "content_type",
              "hook_type",
              "face_visible",
              "motion_intensity"
            ],
            "additionalProperties": false
          },
          "estimated_cost_credits": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Credits POST /v1/generations would debit in the default (single) mode."
          },
          "estimated_duration_sec": {
            "type": "number"
          },
          "avatar": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "name"
                ],
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "storyboard_id",
          "post_id",
          "pipeline_route",
          "generatable",
          "reject_reason",
          "scenes",
          "classification",
          "estimated_cost_credits",
          "estimated_duration_sec",
          "avatar"
        ],
        "additionalProperties": false,
        "$defs": {
          "AnalysisScene": {
            "type": "object",
            "properties": {
              "index": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "duration_sec": {
                "type": "number"
              },
              "description": {
                "type": "string"
              }
            },
            "required": [
              "index",
              "duration_sec",
              "description"
            ],
            "additionalProperties": false,
            "id": "AnalysisScene"
          }
        }
      },
      "CreateGenerationRequest": {
        "type": "object",
        "properties": {
          "source_url": {
            "type": "string",
            "maxLength": 2048,
            "format": "uri",
            "description": "Public Instagram Reel or TikTok video URL. Instagram photo posts and TikTok photo carousels are rejected.",
            "example": "https://www.instagram.com/reel/CxAmpLe123/"
          },
          "avatar_id": {
            "description": "Avatar to place in the video. Defaults to the last one used in this workspace.",
            "type": "string"
          },
          "mode": {
            "description": "`single` (default) renders one cohesive clip capped by the model (15s face-replace, 30s motion copy). `multi` keeps the full scene split and may drift between cuts.",
            "type": "string",
            "enum": [
              "single",
              "multi"
            ]
          },
          "kling_orientation": {
            "description": "Motion-copy (B_KLING) sources only. `video` (default) copies up to 30s of source motion; `image` caps at 10s but holds the outfit better. Changes the price.",
            "type": "string",
            "enum": [
              "image",
              "video"
            ]
          }
        },
        "required": [
          "source_url"
        ]
      },
      "GenerationCreated": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Poll this at GET /v1/generations/{id}."
          },
          "storyboard_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "const": "queued"
          },
          "cost_credits": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Credits debited by this call."
          },
          "scene_count": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "estimated_seconds": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Upper bound on wall-clock time until the video is ready (~25 minutes)."
          },
          "poll_url": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "storyboard_id",
          "status",
          "cost_credits",
          "scene_count",
          "estimated_seconds",
          "poll_url"
        ],
        "additionalProperties": false
      },
      "GenerationStatus": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "storyboard_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "processing",
              "completed",
              "failed"
            ]
          },
          "scene_count": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "video_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Final vertical MP4 once `status` is `completed`. Null before that."
          },
          "shots": {
            "type": "array",
            "items": {
              "$ref": "#/$defs/GenerationShot"
            }
          }
        },
        "required": [
          "id",
          "storyboard_id",
          "status",
          "scene_count",
          "video_url",
          "shots"
        ],
        "additionalProperties": false,
        "$defs": {
          "GenerationShot": {
            "type": "object",
            "properties": {
              "scene_index": {
                "anyOf": [
                  {
                    "type": "integer",
                    "minimum": -9007199254740991,
                    "maximum": 9007199254740991
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "status": {
                "type": "string",
                "enum": [
                  "queued",
                  "processing",
                  "completed",
                  "failed"
                ]
              },
              "duration_sec": {
                "type": "number"
              },
              "video_url": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "error": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "scene_index",
              "status",
              "duration_sec",
              "video_url",
              "error"
            ],
            "additionalProperties": false,
            "id": "GenerationShot"
          }
        }
      },
      "PricingResponse": {
        "type": "object",
        "properties": {
          "currency": {
            "type": "string",
            "const": "USD"
          },
          "credit_usd_floor": {
            "type": "number",
            "description": "Cheapest USD-per-credit rate available (largest pack). Display conversions use it."
          },
          "analysis_credits": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Cost of POST /v1/analyses. Free today."
          },
          "models": {
            "type": "array",
            "items": {
              "$ref": "#/$defs/PricingModel"
            }
          },
          "credit_packs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "credits": {
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                },
                "price_usd": {
                  "type": "number"
                },
                "usd_per_credit": {
                  "type": "number"
                }
              },
              "required": [
                "label",
                "credits",
                "price_usd",
                "usd_per_credit"
              ],
              "additionalProperties": false
            }
          },
          "subscription": {
            "type": "object",
            "properties": {
              "label": {
                "type": "string"
              },
              "price_usd_month": {
                "type": "number"
              },
              "credits_month": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              }
            },
            "required": [
              "label",
              "price_usd_month",
              "credits_month"
            ],
            "additionalProperties": false
          },
          "topup_url": {
            "type": "string"
          }
        },
        "required": [
          "currency",
          "credit_usd_floor",
          "analysis_credits",
          "models",
          "credit_packs",
          "subscription",
          "topup_url"
        ],
        "additionalProperties": false,
        "$defs": {
          "PricingModel": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "label": {
                "type": "string"
              },
              "pricing": {
                "type": "string",
                "enum": [
                  "flat",
                  "per_second"
                ]
              },
              "credits": {
                "type": "number",
                "description": "Flat credits per run, or credits per output second for `per_second` models."
              },
              "max_output_sec": {
                "type": "number"
              },
              "notes": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "label",
              "pricing",
              "credits",
              "max_output_sec",
              "notes"
            ],
            "additionalProperties": false,
            "id": "PricingModel"
          }
        }
      },
      "AvatarListResponse": {
        "type": "object",
        "properties": {
          "avatars": {
            "type": "array",
            "items": {
              "$ref": "#/$defs/AvatarSummary"
            }
          }
        },
        "required": [
          "avatars"
        ],
        "additionalProperties": false,
        "$defs": {
          "AvatarSummary": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "status": {
                "type": "string",
                "description": "Only READY avatars can be used for a generation."
              },
              "photo_url": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "created_at": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name",
              "status",
              "photo_url",
              "created_at"
            ],
            "additionalProperties": false,
            "id": "AvatarSummary"
          }
        }
      },
      "MeResponse": {
        "type": "object",
        "properties": {
          "workspace_id": {
            "type": "string"
          },
          "balance_credits": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "plan": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Subscription tier key, or null on pay-as-you-go."
          },
          "key_prefix": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "topup_url": {
            "type": "string"
          }
        },
        "required": [
          "workspace_id",
          "balance_credits",
          "plan",
          "key_prefix",
          "scopes",
          "topup_url"
        ],
        "additionalProperties": false
      },
      "Problem": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "Stable URI identifying the failure class."
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "code": {
            "type": "string",
            "description": "The failure class as a bare token — branch on this."
          },
          "detail": {
            "type": "string"
          },
          "required_credits": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "balance_credits": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "topup_url": {
            "type": "string"
          },
          "reason": {
            "description": "Internal pipeline reject reason, when one applies.",
            "type": "string"
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "code",
          "detail"
        ],
        "additionalProperties": false
      }
    }
  }
}