{
  "openapi": "3.1.0",
  "info": {
    "title": "Swap API",
    "version": "1.0.0",
    "summary": "Troca de personagem, imagem para vídeo e texto para vídeo, cobrados em créditos.",
    "description": "API HTTP da Swap. Todas as respostas de sucesso usam o envelope `{\"data\": ...}`; erros usam `{\"error\":{\"code\",\"message\"},\"requestId\"}`. Datas numéricas estão em milissegundos desde 1970 (UTC). Use a chave somente no servidor.",
    "contact": {
      "name": "Swap",
      "url": "https://app.tryaswap.com/desenvolvedores"
    }
  },
  "servers": [
    {
      "url": "https://api.tryaswap.com",
      "description": "Produção"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Conta",
      "description": "Identidade da chave, saldo e extrato."
    },
    {
      "name": "Arquivos",
      "description": "Vídeos e imagens privados usados nas gerações."
    },
    {
      "name": "Gerações",
      "description": "Cotação, criação, acompanhamento e download."
    },
    {
      "name": "Personagens",
      "description": "Fichas criadas no estúdio."
    },
    {
      "name": "Público",
      "description": "Rotas sem autenticação."
    }
  ],
  "paths": {
    "/v1/capabilities": {
      "get": {
        "tags": [
          "Público"
        ],
        "operationId": "getCapabilities",
        "summary": "Recursos disponíveis",
        "security": [],
        "description": "Informa se o motor de geração está conectado, os modos aceitos, limites de upload e o estado do checkout. Consulte antes de habilitar gerações no seu produto.",
        "responses": {
          "200": {
            "description": "Recursos",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CapabilitiesEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/plans": {
      "get": {
        "tags": [
          "Público"
        ],
        "operationId": "listPlans",
        "summary": "Pacotes e assinaturas à venda",
        "security": [],
        "description": "Ofertas configuradas no servidor. A compra é feita pelo painel em app.tryaswap.com; chaves de API não compram créditos.",
        "responses": {
          "200": {
            "description": "Ofertas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "checkoutAvailable": {
                          "type": "boolean"
                        },
                        "offers": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Offer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "tags": [
          "Conta"
        ],
        "operationId": "getMe",
        "summary": "Identidade da chave",
        "description": "Retorna o usuário dono da chave, o ID da chave e as permissões. Não exige permissão específica.",
        "responses": {
          "200": {
            "description": "Identidade",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Me"
                    }
                  }
                },
                "example": {
                  "data": {
                    "userId": "usr_8Kf2…",
                    "authKind": "api_key",
                    "keyId": "key_3f0c9a1e5b7d4c2a8e6f1b0d9c7a5e3f",
                    "scopes": [
                      "jobs:write",
                      "jobs:read",
                      "assets:write",
                      "balance:read"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/balance": {
      "get": {
        "tags": [
          "Conta"
        ],
        "operationId": "getBalance",
        "summary": "Saldo de créditos",
        "x-scope": "balance:read",
        "description": "Saldo efetivo da conta, o mesmo do estúdio e do app. `available + reserved + spent + revoked = granted + debt`.",
        "responses": {
          "200": {
            "description": "Saldo",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Balance"
                    }
                  }
                },
                "example": {
                  "data": {
                    "available": 840,
                    "reserved": 60,
                    "spent": 1100,
                    "granted": 2000,
                    "revoked": 0,
                    "debt": 0,
                    "frozen": false,
                    "unit": "credits"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "tags": [
          "Conta"
        ],
        "operationId": "listUsage",
        "summary": "Extrato de créditos",
        "x-scope": "usage:read",
        "description": "Movimentos do ledger, do mais recente ao mais antigo: `grant` (crédito adicionado), `reserve` (reserva de uma geração), `settle` (consumo final) e `release` (devolução).",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Página do extrato",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/LedgerEntry"
                          }
                        },
                        "nextCursor": {
                          "$ref": "#/components/schemas/NextCursor"
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/assets": {
      "post": {
        "tags": [
          "Arquivos"
        ],
        "operationId": "uploadAsset",
        "summary": "Enviar arquivo",
        "x-scope": "assets:write",
        "description": "Envia o arquivo como corpo binário. JPEG, PNG, WebP, MP4 ou WebM, até 20 MiB. Cota por conta: 256 MiB, 100 arquivos e 20 envios por hora. Repetir o mesmo `Idempotency-Key` com os mesmos bytes devolve o arquivo já criado (`replayed: true`).",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "X-Swap-Filename",
            "in": "header",
            "required": true,
            "description": "Nome do arquivo codificado com `encodeURIComponent` (até 180 caracteres).",
            "schema": {
              "type": "string"
            },
            "example": "minha-cena.mp4"
          },
          {
            "name": "Content-Type",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "image/jpeg",
                "image/png",
                "image/webp",
                "video/mp4",
                "video/webm"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Arquivo criado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "200": {
            "description": "Repetição idempotente do mesmo envio",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/Error"
          },
          "415": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "get": {
        "tags": [
          "Arquivos"
        ],
        "operationId": "listAssets",
        "summary": "Listar arquivos",
        "x-scope": "assets:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Arquivos prontos e cota",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Asset"
                          }
                        },
                        "nextCursor": {
                          "$ref": "#/components/schemas/NextCursor"
                        },
                        "quota": {
                          "$ref": "#/components/schemas/Quota"
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/assets/{assetId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AssetId"
        }
      ],
      "get": {
        "tags": [
          "Arquivos"
        ],
        "operationId": "getAsset",
        "summary": "Metadados do arquivo",
        "x-scope": "assets:read",
        "responses": {
          "200": {
            "description": "Arquivo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Asset"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Arquivos"
        ],
        "operationId": "deleteAsset",
        "summary": "Excluir arquivo",
        "x-scope": "assets:write",
        "description": "Arquivos usados por um projeto, personagem ou publicação retornam 409 até que a referência seja removida.",
        "responses": {
          "200": {
            "description": "Excluído",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "const": true
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/assets/{assetId}/content": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AssetId"
        }
      ],
      "get": {
        "tags": [
          "Arquivos"
        ],
        "operationId": "getAssetContent",
        "summary": "Baixar bytes do arquivo",
        "x-scope": "assets:read",
        "description": "Bytes originais. Aceita `Range: bytes=início-fim` (resposta 206).",
        "parameters": [
          {
            "name": "Range",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "bytes=0-1048575"
          }
        ],
        "responses": {
          "200": {
            "description": "Arquivo completo",
            "content": {
              "*/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "206": {
            "description": "Trecho solicitado"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "416": {
            "description": "Intervalo inválido"
          }
        }
      }
    },
    "/v1/personas": {
      "get": {
        "tags": [
          "Personagens"
        ],
        "operationId": "listPersonas",
        "summary": "Listar personagens",
        "x-scope": "personas:read",
        "description": "Personagens criados no estúdio. Use `id` como `personaId` numa troca de personagem.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Personagens",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Persona"
                          }
                        },
                        "nextCursor": {
                          "$ref": "#/components/schemas/NextCursor"
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/personas/{personaId}": {
      "get": {
        "tags": [
          "Personagens"
        ],
        "operationId": "getPersona",
        "summary": "Ler personagem",
        "x-scope": "personas:read",
        "parameters": [
          {
            "name": "personaId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^prs_[a-f0-9]{32}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Personagem",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Persona"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/jobs/quote": {
      "post": {
        "tags": [
          "Gerações"
        ],
        "operationId": "quoteJob",
        "summary": "Cotar geração",
        "x-scope": "jobs:write",
        "description": "Calcula a reserva de créditos sem reservar nada. `sufficient` considera o saldo da conta, pendências financeiras e o teto restante da chave. Valida os arquivos da mesma forma que a criação.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cotação",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Quote"
                    }
                  }
                },
                "example": {
                  "data": {
                    "mode": "character_replacement",
                    "durationSeconds": 5,
                    "reservedCredits": 60,
                    "unit": "credits",
                    "available": 840,
                    "keyRemaining": 400,
                    "sufficient": true
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/EngineUnavailable"
          }
        }
      }
    },
    "/v1/jobs": {
      "post": {
        "tags": [
          "Gerações"
        ],
        "operationId": "createJob",
        "summary": "Criar geração",
        "x-scope": "jobs:write",
        "description": "Reserva os créditos da cotação e coloca a geração na fila (202). Ao terminar, cobra o custo real até o valor reservado e devolve a diferença; se falhar, devolve a reserva inteira. Repetir o mesmo `Idempotency-Key` com o mesmo corpo devolve o mesmo job (`replayed: true`) sem nova cobrança; com outro corpo, 409.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobInput"
              },
              "examples": {
                "troca": {
                  "summary": "Troca de personagem",
                  "value": {
                    "mode": "character_replacement",
                    "sourceAssetId": "asset_9b1e…",
                    "referenceAssetId": "asset_51c0…",
                    "durationSeconds": 5,
                    "aspectRatio": "9:16"
                  }
                },
                "imagem": {
                  "summary": "Imagem para vídeo",
                  "value": {
                    "mode": "image_to_video",
                    "sourceAssetId": "asset_51c0…",
                    "prompt": "Ela acena para a câmera e sorri.",
                    "durationSeconds": 5,
                    "aspectRatio": "9:16"
                  }
                },
                "texto": {
                  "summary": "Texto para vídeo",
                  "value": {
                    "mode": "text_to_video",
                    "prompt": "Um poodle de terno apresenta o jornal da noite.",
                    "durationSeconds": 5,
                    "aspectRatio": "16:9"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Na fila",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "200": {
            "description": "Repetição idempotente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "`insufficient_credits` (saldo da conta) ou `key_credit_limit` (teto da chave)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/EngineUnavailable"
          }
        }
      },
      "get": {
        "tags": [
          "Gerações"
        ],
        "operationId": "listJobs",
        "summary": "Listar gerações",
        "x-scope": "jobs:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Gerações",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Job"
                          }
                        },
                        "nextCursor": {
                          "$ref": "#/components/schemas/NextCursor"
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/jobs/{jobId}": {
      "get": {
        "tags": [
          "Gerações"
        ],
        "operationId": "getJob",
        "summary": "Acompanhar geração",
        "x-scope": "jobs:read",
        "description": "Consulte a cada 5–10 s até `state` ser `succeeded` ou `failed`. Jobs de outra conta retornam 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "200": {
            "description": "Job",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Job"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/jobs/{jobId}/output": {
      "get": {
        "tags": [
          "Gerações"
        ],
        "operationId": "getJobOutput",
        "summary": "Baixar o vídeo gerado",
        "x-scope": "jobs:read",
        "description": "MP4 final da geração concluída. Aceita `Range`. Antes da conclusão retorna 409 `output_not_ready`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "200": {
            "description": "Vídeo",
            "content": {
              "video/mp4": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "206": {
            "description": "Trecho solicitado"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "swap_sk_key_<32 hex>.<64 hex>",
        "description": "Crie a chave em https://app.tryaswap.com/desenvolvedores. O segredo é exibido uma única vez."
      }
    },
    "parameters": {
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 20
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "Valor de `nextCursor` da página anterior.",
        "schema": {
          "type": "string"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "16–128 caracteres `[A-Za-z0-9_.:-]`. Gere um UUID por operação e reutilize-o nas tentativas.",
        "schema": {
          "type": "string",
          "pattern": "^[a-zA-Z0-9_.:-]{16,128}$"
        }
      },
      "AssetId": {
        "name": "assetId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^asset_[a-f0-9]{32}$"
        }
      },
      "JobId": {
        "name": "jobId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^job_[a-f0-9]{32}$"
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Requisição inválida",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Chave ausente, inválida, expirada ou revogada (`api_key_required`, `invalid_api_key`)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "A chave não tem a permissão (`insufficient_scope`) ou a origem do navegador não é permitida (`origin_forbidden`)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Recurso inexistente ou de outra conta",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "Conflito de estado ou de idempotência",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Limite por minuto da chave (`rate_limited`) ou do endereço IP (`ip_rate_limited`). Respeite `Retry-After`.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "EngineUnavailable": {
        "description": "O motor de geração não está conectado (`engine_unavailable`). Nenhum crédito foi reservado.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "example": "insufficient_credits"
              },
              "message": {
                "type": "string",
                "example": "Saldo de créditos insuficiente."
              }
            }
          },
          "requestId": {
            "type": "string",
            "example": "req_4b0e…"
          }
        }
      },
      "NextCursor": {
        "type": [
          "string",
          "null"
        ],
        "description": "Cursor da próxima página ou null no fim."
      },
      "Me": {
        "type": "object",
        "required": [
          "userId",
          "authKind"
        ],
        "properties": {
          "userId": {
            "type": "string"
          },
          "authKind": {
            "type": "string",
            "enum": [
              "api_key",
              "session"
            ]
          },
          "keyId": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            }
          }
        }
      },
      "Scope": {
        "type": "string",
        "enum": [
          "balance:read",
          "usage:read",
          "jobs:read",
          "jobs:write",
          "assets:read",
          "assets:write",
          "personas:read"
        ]
      },
      "Balance": {
        "type": "object",
        "properties": {
          "available": {
            "type": "integer",
            "description": "Créditos livres para novas gerações."
          },
          "reserved": {
            "type": "integer",
            "description": "Reservados por gerações em andamento."
          },
          "spent": {
            "type": "integer"
          },
          "granted": {
            "type": "integer"
          },
          "revoked": {
            "type": "integer",
            "description": "Retirados por estorno."
          },
          "debt": {
            "type": "integer",
            "description": "Ajuste pendente após estorno."
          },
          "frozen": {
            "type": "boolean",
            "description": "Pendência financeira: novas gerações ficam bloqueadas."
          },
          "unit": {
            "const": "credits"
          }
        }
      },
      "LedgerEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "grant",
              "reserve",
              "settle",
              "release"
            ]
          },
          "amount": {
            "type": "integer"
          },
          "jobId": {
            "type": [
              "string",
              "null"
            ]
          },
          "apiKeyId": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "integer"
          }
        }
      },
      "Asset": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "asset_9b1e4c2a7d3f4e8a9c0b1d2e3f4a5b6c"
          },
          "filename": {
            "type": "string"
          },
          "mimeType": {
            "type": "string",
            "enum": [
              "image/jpeg",
              "image/png",
              "image/webp",
              "video/mp4",
              "video/webm"
            ]
          },
          "byteSize": {
            "type": "integer"
          },
          "kind": {
            "type": "string",
            "enum": [
              "image",
              "video"
            ]
          },
          "state": {
            "const": "ready"
          },
          "referenceCount": {
            "type": "integer"
          },
          "createdAt": {
            "type": "integer"
          },
          "contentUrl": {
            "type": "string",
            "example": "/v1/assets/asset_9b1e…/content"
          }
        }
      },
      "UploadEnvelope": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "asset": {
                "$ref": "#/components/schemas/Asset"
              },
              "replayed": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "Quota": {
        "type": "object",
        "properties": {
          "usedBytes": {
            "type": "integer"
          },
          "maxBytes": {
            "type": "integer"
          },
          "usedAssets": {
            "type": "integer"
          },
          "maxAssets": {
            "type": "integer"
          },
          "cleanupPendingAssets": {
            "type": "integer"
          }
        }
      },
      "Persona": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "referencePreset": {
            "type": [
              "string",
              "null"
            ]
          },
          "species": {
            "type": "string"
          },
          "humorStyle": {
            "type": "string"
          },
          "blueprint": {
            "type": [
              "object",
              "null"
            ]
          },
          "identityAssetId": {
            "type": [
              "string",
              "null"
            ]
          },
          "styleAssetId": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "integer"
          },
          "updatedAt": {
            "type": "integer"
          }
        }
      },
      "JobInput": {
        "type": "object",
        "required": [
          "mode"
        ],
        "additionalProperties": false,
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "character_replacement",
              "image_to_video",
              "text_to_video"
            ],
            "description": "`character_replacement`: vídeo em `sourceAssetId` + personagem em `referenceAssetId` ou `personaId`. `image_to_video`: imagem em `sourceAssetId`. `text_to_video`: só `prompt`."
          },
          "prompt": {
            "type": "string",
            "maxLength": 2000,
            "default": ""
          },
          "sourceAssetId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Vídeo (troca) ou imagem (imagem para vídeo) enviado pela mesma conta."
          },
          "referenceAssetId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Imagem do personagem que entra na cena."
          },
          "personaId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Personagem salvo no estúdio, alternativa a `referenceAssetId`."
          },
          "durationSeconds": {
            "type": "integer",
            "minimum": 1,
            "maximum": 30,
            "default": 5
          },
          "aspectRatio": {
            "type": "string",
            "enum": [
              "9:16",
              "16:9",
              "1:1"
            ],
            "default": "9:16"
          }
        }
      },
      "Quote": {
        "type": "object",
        "properties": {
          "mode": {
            "type": "string"
          },
          "durationSeconds": {
            "type": "integer"
          },
          "reservedCredits": {
            "type": "integer"
          },
          "unit": {
            "const": "credits"
          },
          "available": {
            "type": "integer"
          },
          "keyRemaining": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Teto restante da chave; null para sessões."
          },
          "sufficient": {
            "type": "boolean"
          }
        }
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "job_7a2c9e4b1d3f5a6c8e0b2d4f6a8c0e1b"
          },
          "state": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "succeeded",
              "failed"
            ]
          },
          "input": {
            "$ref": "#/components/schemas/JobInput"
          },
          "reservedCredits": {
            "type": "integer"
          },
          "chargedCredits": {
            "type": "integer"
          },
          "output": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "assetId": {
                "type": "string"
              },
              "mimeType": {
                "const": "video/mp4"
              },
              "sha256": {
                "type": "string"
              }
            }
          },
          "outputUrl": {
            "type": [
              "string",
              "null"
            ],
            "example": "/v1/jobs/job_7a2c…/output"
          },
          "errorCode": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "integer"
          },
          "updatedAt": {
            "type": "integer"
          },
          "replayed": {
            "type": "boolean"
          }
        }
      },
      "JobEnvelope": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Job"
          }
        }
      },
      "Offer": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "subscription",
              "credits"
            ]
          },
          "currency": {
            "type": "string",
            "enum": [
              "brl",
              "usd",
              "eur"
            ]
          },
          "amount": {
            "type": "integer",
            "description": "Valor em centavos."
          },
          "minorUnit": {
            "const": 2
          },
          "interval": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "month",
              "year",
              null
            ]
          },
          "credits": {
            "type": "integer"
          }
        }
      },
      "CapabilitiesEnvelope": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "generation": {
                "type": "object",
                "properties": {
                  "available": {
                    "type": "boolean"
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "modes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "uploads": {
                "type": "object",
                "properties": {
                  "available": {
                    "type": "boolean"
                  },
                  "maxFileBytes": {
                    "type": "integer"
                  },
                  "mimeTypes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "api": {
                "type": "object",
                "properties": {
                  "version": {
                    "const": "v1"
                  },
                  "baseUrl": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "docsUrl": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Scope"
                    }
                  }
                }
              },
              "billing": {
                "type": "object",
                "properties": {
                  "checkoutAvailable": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        }
      }
    },
    "headers": {
      "RateLimitLimit": {
        "description": "Requisições por minuto permitidas para a chave.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Requisições restantes na janela atual.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Fim da janela atual, em segundos Unix.",
        "schema": {
          "type": "integer"
        }
      },
      "RequestId": {
        "description": "Identificador para suporte.",
        "schema": {
          "type": "string"
        }
      }
    }
  }
}