{
  "openapi": "3.0.3",
  "info": {
    "title": "Astorya Partner API",
    "version": "1.0.0",
    "description": "API servidor-a-servidor para parceiros B2B: login único (SSO) em WebView, gestão de membros, catálogo e relatórios de uso.\n\n> Especificação provisória escrita à mão. Será substituída pela gerada no backend (`npm run sync:openapi`).",
    "contact": {
      "name": "Suporte técnico Astorya"
    }
  },
  "servers": [
    {
      "url": "https://api.astorya.com.br",
      "description": "Produção e sandbox (o ambiente é definido pela credencial)"
    }
  ],
  "security": [
    {
      "partnerBasic": []
    }
  ],
  "tags": [
    {
      "name": "Sessões",
      "description": "Login único: gera a launchUrl que o app abre na WebView."
    },
    {
      "name": "Membros",
      "description": "Cadastro, consulta e revogação de membros pelo externalUserId."
    },
    {
      "name": "Catálogo",
      "description": "Títulos disponíveis para os membros."
    },
    {
      "name": "Uso",
      "description": "Relatórios mensais de consumo."
    }
  ],
  "paths": {
    "/partner/v1/sessions": {
      "post": {
        "tags": [
          "Sessões"
        ],
        "operationId": "createSession",
        "summary": "Criar sessão (login único)",
        "description": "Cria ou encontra o membro pelo `externalUserId` e devolve uma `launchUrl` válida por 60 segundos e de uso único. Chame só do seu backend.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSessionRequest"
              },
              "example": {
                "externalUserId": "aluno-10293",
                "email": "maria@exemplo.com.br",
                "name": "Maria Souza",
                "redirectPath": "/biblioteca"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sessão criada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Validation"
          },
          "401": {
            "$ref": "#/components/responses/InvalidClient"
          },
          "403": {
            "$ref": "#/components/responses/UserRevoked"
          },
          "409": {
            "$ref": "#/components/responses/EmailConflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/partner/v1/users/{externalUserId}": {
      "parameters": [
        {
          "name": "externalUserId",
          "in": "path",
          "required": true,
          "description": "Identificador do membro no sistema do parceiro (codificado para URL).",
          "schema": {
            "type": "string",
            "maxLength": 128
          },
          "example": "aluno-10293"
        }
      ],
      "put": {
        "tags": [
          "Membros"
        ],
        "operationId": "upsertUser",
        "summary": "Criar ou atualizar membro",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpsertUserRequest"
              },
              "example": {
                "name": "Maria Souza Lima",
                "email": "maria.lima@exemplo.com.br",
                "status": "ACTIVE"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Membro criado ou atualizado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerUser"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Validation"
          },
          "401": {
            "$ref": "#/components/responses/InvalidClient"
          },
          "409": {
            "$ref": "#/components/responses/EmailConflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "get": {
        "tags": [
          "Membros"
        ],
        "operationId": "getUser",
        "summary": "Consultar membro",
        "responses": {
          "200": {
            "description": "Membro encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerUserWithActivity"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidClient"
          },
          "404": {
            "description": "Membro não encontrado no seu tenant."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Membros"
        ],
        "operationId": "revokeUser",
        "summary": "Revogar acesso",
        "description": "Revoga o acesso imediatamente e encerra as sessões ativas. Reative com PUT `status: ACTIVE`.",
        "responses": {
          "204": {
            "description": "Acesso revogado."
          },
          "401": {
            "$ref": "#/components/responses/InvalidClient"
          },
          "404": {
            "description": "Membro não encontrado no seu tenant."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/partner/v1/catalog": {
      "get": {
        "tags": [
          "Catálogo"
        ],
        "operationId": "listCatalog",
        "summary": "Listar catálogo",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "audiobook",
                "ebook"
              ]
            },
            "description": "Filtra por formato."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1
            },
            "description": "Itens por página."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "O `nextCursor` da página anterior."
          }
        ],
        "responses": {
          "200": {
            "description": "Uma página do catálogo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Validation"
          },
          "401": {
            "$ref": "#/components/responses/InvalidClient"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/partner/v1/usage": {
      "get": {
        "tags": [
          "Uso"
        ],
        "operationId": "getUsage",
        "summary": "Relatório de uso do mês",
        "parameters": [
          {
            "name": "month",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
            },
            "example": "2026-09"
          }
        ],
        "responses": {
          "200": {
            "description": "Relatório do mês.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageReport"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Validation"
          },
          "401": {
            "$ref": "#/components/responses/InvalidClient"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "partnerBasic": {
        "type": "http",
        "scheme": "basic",
        "description": "`Authorization: Basic base64(client_id:client_secret)`. O client_id tem o formato `ast_live_XXXXXXXXXXXXXXXXXXXXXXXX`."
      }
    },
    "schemas": {
      "CreateSessionRequest": {
        "type": "object",
        "required": [
          "externalUserId"
        ],
        "properties": {
          "externalUserId": {
            "type": "string",
            "maxLength": 128,
            "description": "Identificador estável do membro no sistema do parceiro."
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": "string"
          },
          "redirectPath": {
            "type": "string",
            "pattern": "^/",
            "description": "Caminho do Astorya Web para abrir após o login (ex.: /biblioteca, /player/{id})."
          }
        }
      },
      "SessionResponse": {
        "type": "object",
        "required": [
          "launchUrl",
          "launchToken",
          "expiresAt",
          "user"
        ],
        "properties": {
          "launchUrl": {
            "type": "string",
            "format": "uri",
            "example": "https://play.astorya.com.br/launch?token=5JxR2mVt9wQ8aLz3nB7cYd4fHk6pE1sU&redirect=%2Fbiblioteca"
          },
          "launchToken": {
            "type": "string",
            "example": "5JxR2mVt9wQ8aLz3nB7cYd4fHk6pE1sU"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "60 segundos após a criação.",
            "example": "2026-10-08T14:31:00.000Z"
          },
          "user": {
            "$ref": "#/components/schemas/PartnerUser"
          }
        }
      },
      "UpsertUserRequest": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "ACTIVE",
              "BLOCKED"
            ]
          }
        }
      },
      "PartnerUser": {
        "type": "object",
        "required": [
          "id",
          "externalUserId",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "6702f1c9a8b4e2d3c1f0a9b8"
          },
          "externalUserId": {
            "type": "string",
            "example": "aluno-10293"
          },
          "email": {
            "type": "string",
            "nullable": true,
            "example": "maria@exemplo.com.br"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "example": "Maria Souza"
          },
          "status": {
            "type": "string",
            "enum": [
              "ACTIVE",
              "BLOCKED"
            ]
          }
        }
      },
      "PartnerUserWithActivity": {
        "allOf": [
          {
            "$ref": "#/components/schemas/PartnerUser"
          },
          {
            "type": "object",
            "properties": {
              "lastActivityAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "example": "2026-10-07T21:12:44.000Z"
              }
            }
          }
        ]
      },
      "CatalogItem": {
        "type": "object",
        "required": [
          "id",
          "type",
          "title",
          "author",
          "coverUrl"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "audiobook",
              "ebook"
            ]
          },
          "title": {
            "type": "string"
          },
          "author": {
            "type": "string"
          },
          "coverUrl": {
            "type": "string",
            "format": "uri"
          },
          "durationSeconds": {
            "type": "integer",
            "description": "Só em audiolivros."
          },
          "chapters": {
            "type": "integer",
            "description": "Só em e-books."
          }
        }
      },
      "CatalogPage": {
        "type": "object",
        "required": [
          "items",
          "nextCursor"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogItem"
            }
          },
          "nextCursor": {
            "type": "string",
            "nullable": true,
            "description": "null na última página."
          }
        }
      },
      "UsageReport": {
        "type": "object",
        "required": [
          "month",
          "totals",
          "users"
        ],
        "properties": {
          "month": {
            "type": "string",
            "example": "2026-09"
          },
          "totals": {
            "type": "object",
            "properties": {
              "activeUsers": {
                "type": "integer"
              },
              "titlesStarted": {
                "type": "integer"
              },
              "listeningSeconds": {
                "type": "integer"
              },
              "readingChapters": {
                "type": "integer"
              }
            }
          },
          "users": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "externalUserId": {
                  "type": "string"
                },
                "titlesStarted": {
                  "type": "integer"
                },
                "listeningSeconds": {
                  "type": "integer"
                },
                "lastActivityAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true,
                  "example": "2026-10-07T21:12:44.000Z"
                }
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "statusCode",
          "code",
          "message"
        ],
        "properties": {
          "statusCode": {
            "type": "integer"
          },
          "code": {
            "type": "string",
            "enum": [
              "INVALID_CLIENT",
              "USER_REVOKED",
              "EMAIL_CONFLICT",
              "INVALID_LAUNCH_TOKEN",
              "VALIDATION",
              "RATE_LIMITED"
            ]
          },
          "message": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          }
        }
      }
    },
    "responses": {
      "Validation": {
        "description": "Requisição inválida (`VALIDATION`); `message` é uma lista.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "statusCode": 400,
              "code": "VALIDATION",
              "message": [
                "externalUserId should not be empty"
              ]
            }
          }
        }
      },
      "InvalidClient": {
        "description": "Credencial ausente, inválida ou revogada (`INVALID_CLIENT`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "statusCode": 401,
              "code": "INVALID_CLIENT",
              "message": "Credencial inválida."
            }
          }
        }
      },
      "UserRevoked": {
        "description": "Membro revogado ou bloqueado (`USER_REVOKED`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "statusCode": 403,
              "code": "USER_REVOKED",
              "message": "O acesso deste usuário foi revogado."
            }
          }
        }
      },
      "EmailConflict": {
        "description": "E-mail pertence a outro membro (`EMAIL_CONFLICT`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "statusCode": 409,
              "code": "EMAIL_CONFLICT",
              "message": "Este e-mail já pertence a outro usuário."
            }
          }
        }
      },
      "RateLimited": {
        "description": "Mais de 120 requisições por minuto (`RATE_LIMITED`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "statusCode": 429,
              "code": "RATE_LIMITED",
              "message": "Muitas requisições. Tente novamente em instantes."
            }
          }
        }
      }
    }
  }
}