{"openapi":"3.1.0","info":{"title":"HotLearn API","version":"1.0.0-beta","summary":"API pública do HotLearn: acervo, materiais e integrações (LMS, MCP, scripts).","description":"Fonte única da referência publicada em /desenvolvedores/referencia. Mantida junto com o código: toda rota nova de /api/v1 entra aqui na mesma entrega (um teste falha se faltar). Textos em português; `x-translations` traz inglês e espanhol para a página pública.","contact":{"name":"HotLearn","url":"https://hotlearn.com.br/desenvolvedores","email":"contatos@estudiosite.com"}},"servers":[{"url":"https://hotlearn.com.br"}],"tags":[{"name":"discovery","description":"Descoberta automática dos endereços de autorização.","x-translations":{"en":{"name":"Discovery","description":"Automatic discovery of the authorization endpoints."},"es":{"name":"Descubrimiento","description":"Descubrimiento automático de las direcciones de autorización."}},"x-name":"Descoberta"},{"name":"oauth","description":"Autorização OAuth 2.1: pedir acesso, trocar o código pela chave, renovar e revogar.","x-translations":{"en":{"name":"Authorization","description":"OAuth 2.1 authorization: request access, exchange the code for a key, refresh and revoke."},"es":{"name":"Autorización","description":"Autorización OAuth 2.1: pedir acceso, cambiar el código por la clave, renovar y revocar."}},"x-name":"Autorização"},{"name":"account","description":"A conta conectada e a própria conexão.","x-translations":{"en":{"name":"Account and connection","description":"The connected account and the connection itself."},"es":{"name":"Cuenta y conexión","description":"La cuenta conectada y la propia conexión."}},"x-name":"Conta e conexão"},{"name":"catalog","description":"Acervo: produtos, sumário, prévia, capa, categorias e licença.","x-name":"Acervo","x-translations":{"en":{"name":"Catalog","description":"Catalog: products, table of contents, preview, cover, categories and license."},"es":{"name":"Acervo","description":"Acervo: productos, sumario, vista previa, portada, categorías y licencia."}}},{"name":"acquisitions","x-name":"Aquisições","description":"Adquirir produtos com os créditos da conta e ver o histórico.","x-translations":{"en":{"name":"Acquisitions","description":"Acquire products with the account's credits and see the history."},"es":{"name":"Adquisiciones","description":"Adquirir productos con los créditos de la cuenta y ver el historial."}}},{"name":"materials","x-name":"Materiais e arquivos","description":"Materiais de um produto (matriz, curso SCORM, quadro) e pedidos de arquivo.","x-translations":{"en":{"name":"Materials and files","description":"A product's materials (matrix, SCORM course, whiteboard) and file requests."},"es":{"name":"Materiales y archivos","description":"Materiales de un producto (matriz, curso SCORM, pizarra) y solicitudes de archivo."}}},{"name":"publications","x-name":"Publicações","description":"O que o aplicativo criou do lado dele a partir dos materiais.","x-translations":{"en":{"name":"Publications","description":"What the app created on its side from the materials."},"es":{"name":"Publicaciones","description":"Lo que la aplicación creó de su lado a partir de los materiales."}}},{"name":"docs","description":"Esta especificação.","x-translations":{"en":{"name":"Documentation","description":"This specification."},"es":{"name":"Documentación","description":"Esta especificación."}},"x-name":"Documentação"}],"paths":{"/.well-known/oauth-authorization-server":{"get":{"operationId":"authorizationServerMetadata","tags":["discovery"],"summary":"Metadados do servidor de autorização","description":"Documento RFC 8414 com os endereços de autorização, troca e revogação, os escopos aceitos e o método PKCE (`S256`).","x-translations":{"en":{"summary":"Authorization server metadata","description":"RFC 8414 document with the authorization, token and revocation endpoints, supported scopes and the PKCE method (`S256`)."},"es":{"summary":"Metadatos del servidor de autorización","description":"Documento RFC 8414 con las direcciones de autorización, canje y revocación, los alcances aceptados y el método PKCE (`S256`)."}},"security":[],"responses":{"200":{"description":"Metadados","content":{"application/json":{"example":{"issuer":"https://hotlearn.com.br","authorization_endpoint":"https://hotlearn.com.br/oauth/authorize","token_endpoint":"https://hotlearn.com.br/oauth/token","revocation_endpoint":"https://hotlearn.com.br/oauth/revoke","scopes_supported":["catalog:public","account:read","catalog:read"],"response_types_supported":["code"],"grant_types_supported":["authorization_code","refresh_token","client_credentials"],"code_challenge_methods_supported":["S256"],"token_endpoint_auth_methods_supported":["client_secret_post","client_secret_basic","none"],"service_documentation":"https://hotlearn.com.br/desenvolvedores"}}}}}}},"/.well-known/oauth-protected-resource":{"get":{"operationId":"protectedResourceMetadata","tags":["discovery"],"summary":"Metadados da API (recurso protegido)","description":"Documento RFC 9728 da `/api/v1`, apontando para o servidor de autorização. Também responde em `/.well-known/oauth-protected-resource/api/v1`. Usado por clientes MCP.","x-translations":{"en":{"summary":"API metadata (protected resource)","description":"RFC 9728 document for `/api/v1`, pointing to the authorization server. Also served at `/.well-known/oauth-protected-resource/api/v1`. Used by MCP clients."},"es":{"summary":"Metadatos de la API (recurso protegido)","description":"Documento RFC 9728 de `/api/v1`, que apunta al servidor de autorización. También responde en `/.well-known/oauth-protected-resource/api/v1`. Lo usan los clientes MCP."}},"security":[],"responses":{"200":{"description":"Metadados","content":{"application/json":{"example":{"resource":"https://hotlearn.com.br/api/v1","authorization_servers":["https://hotlearn.com.br"],"scopes_supported":["catalog:public","account:read","catalog:read"],"bearer_methods_supported":["header"],"resource_name":"HotLearn API","resource_documentation":"https://hotlearn.com.br/desenvolvedores"}}}}}}},"/oauth/authorize":{"get":{"operationId":"authorize","tags":["oauth"],"summary":"Pedir autorização (abre a tela do HotLearn)","description":"Abra este endereço no navegador da pessoa. Ela entra no HotLearn (se ainda não estiver), vê o que o aplicativo poderá fazer e autoriza. Só o **dono** de uma conta com assinatura ativa ou em teste autoriza. Ao autorizar, o HotLearn volta para o `redirect_uri` com `code` e `state`; ao negar, com `error=access_denied`. Endereço de retorno fora do cadastro do aplicativo não volta para ele: o HotLearn mostra uma página de erro.\n\nAntes de tudo (inclusive antes de mandar ao cadastro com `signup=1`), o HotLearn confere o pedido: PKCE `S256` (senão, código `invalid_request`) e `external_id` (`external_id_required`, `invalid_external_id`). Pedido recusado mostra a página de erro com status `400`, sem voltar ao aplicativo.\n\n**Reconectar:** se a conta já tem conexão ativa com o mesmo aplicativo, o mesmo `external_id` e o mesmo `redirect_uri` exato (e escopos já aprovados), o HotLearn aprova sem mostrar a tela e só emite uma chave nova: as chaves antigas continuam valendo e `display_name`/`url` **não** mudam. Quando o dono aprova na tela (sempre numa conexão nova, ou quando o aplicativo manda `prompt=consent`), a conexão é substituída: as chaves antigas param e o nome e o endereço são atualizados.","x-translations":{"en":{"summary":"Request authorization (opens the HotLearn screen)","description":"Open this address in the person's browser. They sign in to HotLearn (if not yet), see what the app will be able to do and authorize it. Only the **owner** of an account with an active or trial subscription can authorize. When authorized, HotLearn returns to `redirect_uri` with `code` and `state`; when denied, with `error=access_denied`. A return address outside the app's registration never receives the person: HotLearn shows an error page.\n\nBefore anything else (including before sending to sign-up with `signup=1`), HotLearn checks the request: PKCE `S256` (otherwise, code `invalid_request`) and `external_id` (`external_id_required`, `invalid_external_id`). A rejected request shows the error page with status `400`, without returning to the app.\n\n**Reconnecting:** if the account already has an active connection with the same app, the same `external_id` and the same exact `redirect_uri` (and already approved scopes), HotLearn approves without showing the screen and only issues a new key: the old keys keep working and `display_name`/`url` are **not** updated. When the owner approves on the screen (always for a new connection, or when the app sends `prompt=consent`), the connection is replaced: the old keys stop working and the name and address are updated."},"es":{"summary":"Pedir autorización (abre la pantalla de HotLearn)","description":"Abre esta dirección en el navegador de la persona. Ella entra en HotLearn (si aún no lo hizo), ve lo que la aplicación podrá hacer y autoriza. Solo el **dueño** de una cuenta con suscripción activa o en prueba autoriza. Al autorizar, HotLearn vuelve a `redirect_uri` con `code` y `state`; al negar, con `error=access_denied`. Una dirección de regreso fuera del registro de la aplicación nunca recibe a la persona: HotLearn muestra una página de error.\n\nAntes de todo (incluso antes de enviar al registro con `signup=1`), HotLearn revisa el pedido: PKCE `S256` (si no, código `invalid_request`) y `external_id` (`external_id_required`, `invalid_external_id`). Un pedido rechazado muestra la página de error con estado `400`, sin volver a la aplicación.\n\n**Reconectar:** si la cuenta ya tiene una conexión activa con la misma aplicación, el mismo `external_id` y el mismo `redirect_uri` exacto (y alcances ya aprobados), HotLearn aprueba sin mostrar la pantalla y solo emite una clave nueva: las claves antiguas siguen valiendo y `display_name`/`url` **no** cambian. Cuando el dueño aprueba en la pantalla (siempre en una conexión nueva, o cuando la aplicación envía `prompt=consent`), la conexión se reemplaza: las claves antiguas dejan de valer y se actualizan el nombre y la dirección."}},"security":[],"x-browser":true,"parameters":[{"name":"response_type","in":"query","required":true,"schema":{"type":"string","const":"code"},"description":"Sempre `code`."},{"name":"client_id","in":"query","required":true,"schema":{"type":"string"},"description":"Identificador do aplicativo, recebido no cadastro."},{"name":"redirect_uri","in":"query","required":true,"schema":{"type":"string","format":"uri"},"description":"Endereço de retorno cadastrado (aceita padrão de subdomínio, ex.: `https://*.exemplo.com/retorno`). Em produção, só `https`; `http` vale apenas fora de produção (testes)."},{"name":"scope","in":"query","required":true,"schema":{"type":"string"},"description":"Escopos separados por espaço, ex.: `account:read catalog:read`."},{"name":"state","in":"query","required":true,"schema":{"type":"string"},"description":"Valor aleatório seu; volta igual no retorno. Confira-o. Com `signup=1`, a pessoa pode voltar até 2 horas depois com o mesmo `state`: guarde-o por pelo menos 2 horas (recomendado: 2 h 15 min)."},{"name":"code_challenge","in":"query","required":true,"schema":{"type":"string"},"description":"PKCE: `BASE64URL(SHA256(code_verifier))`."},{"name":"code_challenge_method","in":"query","required":true,"schema":{"type":"string","const":"S256"},"description":"Sempre `S256`."},{"name":"external_id","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9._:-]{1,64}$"},"description":"Identificador do cliente do seu lado (ex.: a escola; o id do tenant, um UUID cabe). Cada (conta, aplicativo, `external_id`) é uma conexão. De 1 a 64 caracteres entre `A-Z a-z 0-9 . _ : -`; fora disso (inclusive mais de 64), página de erro `400` com o código `invalid_external_id` — nunca é cortado. Opcional para aplicativo de uma conta só (ex.: assistente de IA); **obrigatório** para aplicativo cadastrado como de várias escolas (como o EstudioLMS): sem ele, página de erro `400` com o código `external_id_required`."},{"name":"display_name","in":"query","required":false,"schema":{"type":"string","maxLength":160},"description":"Opcional. Nome para mostrar (\"Em nome de …\"). Só é aceito quando o host de `url` é o mesmo do `redirect_uri`."},{"name":"url","in":"query","required":false,"schema":{"type":"string","format":"uri"},"description":"Opcional. Endereço do seu lado (ex.: a escola), do mesmo host do `redirect_uri`."},{"name":"prompt","in":"query","required":false,"schema":{"type":"string","enum":["consent"]},"description":"Opcional. `consent` mostra a tela mesmo quando a conexão já existe. Aprovar na tela substitui a conexão: as chaves antigas param e o nome e o endereço são atualizados. Use quando quiser trocar ou atualizar a conexão."},{"name":"signup","in":"query","required":false,"schema":{"type":"integer","enum":[1]},"description":"Opcional. Quem não está logado vai ao cadastro do HotLearn (aviso \"Você está assinando por …\") e, depois do plano ou do teste grátis, volta uma vez para esta tela. O pedido fica guardado por 2 horas (guarde o `state` por pelo menos esse tempo). PKCE e `external_id` são conferidos antes de mandar ao cadastro."}],"x-example-url":"https://hotlearn.com.br/oauth/authorize?response_type=code&client_id=SEU_CLIENT_ID&redirect_uri=https%3A%2F%2Fescola.exemplo.com%2Fhotlearn%2Fretorno&scope=account%3Aread%20catalog%3Aread&state=ALEATORIO&code_challenge=DESAFIO&code_challenge_method=S256&external_id=42&display_name=Escola%20Exemplo&url=https%3A%2F%2Fescola.exemplo.com","responses":{"302":{"description":"Volta ao `redirect_uri` com `code` e `state` (ou `error=access_denied`)."},"400":{"description":"Pedido inválido: página de erro do HotLearn, sem redirecionar. Código na página: `invalid_request` (PKCE ausente ou diferente de `S256`), `external_id_required` ou `invalid_external_id`."},"401":{"description":"Aplicativo desconhecido ou retorno fora do cadastro: página de erro do HotLearn, sem redirecionar."}}}},"/oauth/token":{"post":{"operationId":"token","tags":["oauth"],"summary":"Trocar o código pela chave, renovar ou pedir chave de aplicativo","description":"Três usos, pelo `grant_type`:\n\n- `authorization_code`: troca o `code` (vale 10 minutos, uso único) pela chave de acesso e pela chave de renovação. Mande o `code_verifier` do PKCE.\n- `refresh_token`: troca a chave de renovação por um par novo. **A antiga deixa de valer; usá-la de novo desfaz a conexão inteira.**\n- `client_credentials`: só aplicativo confidencial; a chave só vale para a vitrine pública (`catalog:public`).\n\nA chave de acesso vale 1 hora; a de renovação, 30 dias sem uso. Erros seguem o padrão OAuth (`error`, `error_description`). Limite: 60 chamadas/min por aplicativo e endereço. Depois que a conexão foi desfeita, a renovação responde no formato do OAuth (`{\"error\": \"invalid_grant\", …}`, status `400`), e não em `application/problem+json`: peça para o dono autorizar de novo.","x-translations":{"en":{"summary":"Exchange the code for a key, refresh, or get an app key","description":"Three uses, by `grant_type`:\n\n- `authorization_code`: exchanges the `code` (valid for 10 minutes, single use) for the access key and the refresh key. Send the PKCE `code_verifier`.\n- `refresh_token`: exchanges the refresh key for a new pair. **The old one stops working; using it again revokes the whole connection.**\n- `client_credentials`: confidential apps only; the key is only valid for the public showcase (`catalog:public`).\n\nThe access key lasts 1 hour; the refresh key, 30 days without use. Errors follow the OAuth standard (`error`, `error_description`). Limit: 60 calls/min per app and address. After the connection was revoked, a refresh answers in the OAuth format (`{\"error\": \"invalid_grant\", …}`, status `400`), not in `application/problem+json`: ask the owner to authorize again."},"es":{"summary":"Cambiar el código por la clave, renovar o pedir clave de aplicación","description":"Tres usos, según `grant_type`:\n\n- `authorization_code`: cambia el `code` (vale 10 minutos, uso único) por la clave de acceso y la clave de renovación. Envía el `code_verifier` del PKCE.\n- `refresh_token`: cambia la clave de renovación por un par nuevo. **La antigua deja de valer; usarla de nuevo deshace toda la conexión.**\n- `client_credentials`: solo aplicación confidencial; la clave solo vale para la vitrina pública (`catalog:public`).\n\nLa clave de acceso vale 1 hora; la de renovación, 30 días sin uso. Los errores siguen el estándar OAuth (`error`, `error_description`). Límite: 60 llamadas/min por aplicación y dirección. Después de que la conexión se deshizo, la renovación responde en el formato de OAuth (`{\"error\": \"invalid_grant\", …}`, estado `400`), y no en `application/problem+json`: pide al dueño que autorice de nuevo."}},"security":[],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["grant_type","client_id"],"properties":{"grant_type":{"type":"string","enum":["authorization_code","refresh_token","client_credentials"]},"client_id":{"type":"string"},"client_secret":{"type":"string","description":"Só aplicativo confidencial (servidor)."},"code":{"type":"string"},"redirect_uri":{"type":"string","description":"O mesmo usado na autorização."},"code_verifier":{"type":"string"},"refresh_token":{"type":"string"},"scope":{"type":"string"}}},"example":{"grant_type":"authorization_code","client_id":"SEU_CLIENT_ID","client_secret":"SEU_SEGREDO","code":"CODIGO_RECEBIDO","redirect_uri":"https://escola.exemplo.com/hotlearn/retorno","code_verifier":"SEU_VERIFIER"}}}},"responses":{"200":{"description":"Chaves emitidas","content":{"application/json":{"example":{"token_type":"Bearer","expires_in":3600,"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9…","refresh_token":"def50200a1b2c3…"}}}},"400":{"description":"Código ou chave de renovação inválido, vencido ou já usado","content":{"application/json":{"example":{"error":"invalid_grant","error_description":"The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked…"}}}},"401":{"description":"Aplicativo ou segredo inválido","content":{"application/json":{"example":{"error":"invalid_client","error_description":"Client authentication failed"}}}},"429":{"description":"Limite de chamadas estourado (ver `Retry-After`)"}}}},"/oauth/revoke":{"post":{"operationId":"revoke","tags":["oauth"],"summary":"Revogar uma chave ou desconectar","description":"RFC 7009. Com a **chave de renovação**, desfaz a **conexão inteira** (é assim que o aplicativo desconecta). Com a chave de acesso, revoga só ela. Responde 200 também para chave desconhecida, como manda a RFC. Aplicativo confidencial manda o segredo. Limite: 30 chamadas por minuto.","x-translations":{"en":{"summary":"Revoke a key or disconnect","description":"RFC 7009. With the **refresh key**, it revokes the **whole connection** (this is how an app disconnects). With the access key, it revokes only that key. Also answers 200 for an unknown key, as the RFC requires. Confidential apps send the secret. Limit: 30 calls per minute."},"es":{"summary":"Revocar una clave o desconectar","description":"RFC 7009. Con la **clave de renovación**, deshace **toda la conexión** (así se desconecta la aplicación). Con la clave de acceso, revoca solo esa. Responde 200 también para una clave desconocida, como manda la RFC. La aplicación confidencial envía el secreto. Límite: 30 llamadas por minuto."}},"security":[],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["token","client_id"],"properties":{"token":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"type":"string"}}},"example":{"token":"def50200a1b2c3…","client_id":"SEU_CLIENT_ID","client_secret":"SEU_SEGREDO"}}}},"responses":{"200":{"description":"Revogado (ou chave desconhecida)","content":{"application/json":{"example":[]}}},"400":{"description":"`token` ausente","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Bad Request","status":400,"detail":"Envie a chave em `token`.","code":"invalid_request"}}}},"401":{"description":"Aplicativo ou segredo inválido","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Aplicativo desconhecido.","code":"invalid_client"}}}}}}},"/api/v1/me":{"get":{"operationId":"me","tags":["account"],"summary":"A conta conectada","description":"Conta, quem autorizou, plano, situação da assinatura e saldo de créditos. Bom para conferir a conexão logo depois de autorizar. Em `subscription` vêm só `status` (`trialing`, `active` ou `inactive`), `on_trial` e `trial_ends_at`: não há `past_due`, data de renovação nem uso do teste.","x-translations":{"en":{"summary":"The connected account","description":"Account, who authorized, plan, subscription status and credit balance. Good for checking the connection right after authorizing. `subscription` carries only `status` (`trialing`, `active` or `inactive`), `on_trial` and `trial_ends_at`: there is no `past_due`, renewal date or trial usage."},"es":{"summary":"La cuenta conectada","description":"Cuenta, quién autorizó, plan, situación de la suscripción y saldo de créditos. Útil para comprobar la conexión justo después de autorizar. En `subscription` vienen solo `status` (`trialing`, `active` o `inactive`), `on_trial` y `trial_ends_at`: no hay `past_due`, fecha de renovación ni uso de la prueba."}},"security":[{"oauth2":["account:read"]},{"personalKey":["account:read"]}],"responses":{"200":{"description":"Conta","content":{"application/json":{"example":{"data":{"account":{"id":12,"name":"Escola Exemplo"},"user":{"id":34,"name":"Ana Souza","email":"ana@escola.exemplo.com","role":"owner"},"plan":{"name":"Essencial","slug":"essencial"},"subscription":{"status":"active","on_trial":false,"trial_ends_at":null},"credits":{"balance":40,"unlimited":false},"scopes":["account:read","catalog:read"]}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/usage":{"get":{"operationId":"usage","tags":["account"],"summary":"Créditos pelo uso da API","description":"A tabela que vale para **esta** chave: para cada ação medida (`export_pdf_section`, `export_pdf`, `export_scorm`, `acquisition`, `publication`, `login_link`), quantos créditos ela custa, se a cobrança já está ligada (`charging`), a franquia grátis do mês e o limite por dia, com o que já foi usado. Consulte antes de uma operação pesada para avisar a pessoa. Detalhes no guia Créditos e limites de uso.\n\nCom `charging: false`, a regra está em simulação: o uso é contado, mas nenhum crédito sai (`X-Credits-Charged: 0`). O limite por dia vale nos dois casos. O plano Ilimitado não fica isento sozinho: vale o que a tabela diz para ele.","x-translations":{"en":{"summary":"Credits for API usage","description":"The table that applies to **this** key: for each metered action (`export_pdf_section`, `export_pdf`, `export_scorm`, `acquisition`, `publication`, `login_link`), how many credits it costs, whether charging is on (`charging`), the monthly free allowance and the daily limit, with what was already used. Check it before a heavy operation to warn the person. Details in the Credits and usage limits guide.\n\nWith `charging: false`, the rule is in simulation: usage is counted, but no credits are taken (`X-Credits-Charged: 0`). The daily limit applies either way. The unlimited plan is not exempt by itself: what the table says for it applies."},"es":{"summary":"Créditos por el uso de la API","description":"La tabla que vale para **esta** clave: para cada acción medida (`export_pdf_section`, `export_pdf`, `export_scorm`, `acquisition`, `publication`, `login_link`), cuántos créditos cuesta, si el cobro ya está activo (`charging`), la franquicia gratis del mes y el límite por día, con lo que ya se usó. Consúltala antes de una operación pesada para avisar a la persona. Detalles en la guía Créditos y límites de uso.\n\nCon `charging: false`, la regla está en simulación: el uso se cuenta, pero no se descuenta ningún crédito (`X-Credits-Charged: 0`). El límite por día vale en los dos casos. El plan ilimitado no queda exento por sí solo: vale lo que la tabla diga para él."}},"security":[{"oauth2":["account:read"]},{"personalKey":["account:read"]}],"responses":{"200":{"description":"Tabela vigente","content":{"application/json":{"example":{"data":{"balance":4200,"unlimited":false,"actions":[{"action":"export_pdf","credits":20,"charging":true,"free_monthly":20,"used_this_month":20,"daily_limit":50,"used_today":3},{"action":"publication","credits":0,"charging":false,"free_monthly":null,"used_this_month":0,"daily_limit":null,"used_today":0}]}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/connection":{"get":{"operationId":"getConnection","tags":["account"],"summary":"A própria conexão","description":"O que o HotLearn sabe da conexão desta chave: aplicativo, `external_id`, nome e endereço, link \"abrir lá\" e ações. Chave pessoal não tem conexão (404 `no_connection`).","x-translations":{"en":{"summary":"The connection itself","description":"What HotLearn knows about this key's connection: app, `external_id`, name and address, \"open there\" link and actions. A personal key has no connection (404 `no_connection`)."},"es":{"summary":"La propia conexión","description":"Lo que HotLearn sabe de la conexión de esta clave: aplicación, `external_id`, nombre y dirección, enlace \"abrir allá\" y acciones. La clave personal no tiene conexión (404 `no_connection`)."}},"security":[{"oauth2":[]}],"responses":{"200":{"description":"Conexão","content":{"application/json":{"example":{"data":{"id":7,"client":{"id":"01a13fdf-7c67-70b3-a3b7-bb60430403d8","name":"EstudioLMS"},"external_id":"42","display_name":"Escola Exemplo","url":"https://escola.exemplo.com","open_url_template":"https://escola.exemplo.com/hotlearn/enviar?produto={product}&material={material}&acao={action}","actions":[{"key":"course","label":"Criar um curso novo","materials":["apostila"]}],"scopes":["account:read","catalog:read","connection:write"],"authorized_by":{"name":"Ana Souza"},"authorized_at":"2026-10-15T14:02:11-03:00","last_used_at":"2026-10-16T09:40:00-03:00"}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"description":"Chave pessoal (sem conexão)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Esta chave não pertence a uma conexão de aplicativo (chave pessoal).","code":"no_connection"}}}}}},"put":{"operationId":"updateConnection","tags":["account"],"summary":"Ensinar ao HotLearn como abrir o seu lado","description":"Atualiza `display_name`, `url`, `open_url_template` e `actions`. É daí que sai o botão \"Enviar para {display_name}\" nos materiais do assinante. O `open_url_template` precisa ser `https` (em produção) e do mesmo host de `url`; use `{product}`, `{material}` e `{action}` onde precisar. Campos ausentes ficam como estão.","x-translations":{"en":{"summary":"Teach HotLearn how to open your side","description":"Updates `display_name`, `url`, `open_url_template` and `actions`. This is where the \"Send to {display_name}\" button on the subscriber's materials comes from. `open_url_template` must be `https` (in production) and on the same host as `url`; use `{product}`, `{material}` and `{action}` where needed. Missing fields stay as they are."},"es":{"summary":"Enseñar a HotLearn cómo abrir tu lado","description":"Actualiza `display_name`, `url`, `open_url_template` y `actions`. De ahí sale el botón \"Enviar a {display_name}\" en los materiales del suscriptor. `open_url_template` debe ser `https` (en producción) y del mismo host que `url`; usa `{product}`, `{material}` y `{action}` donde haga falta. Los campos ausentes quedan como están."}},"security":[{"oauth2":["connection:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"display_name":{"type":["string","null"],"maxLength":160},"url":{"type":["string","null"],"format":"uri"},"open_url_template":{"type":["string","null"],"maxLength":500},"actions":{"type":["array","null"],"maxItems":10,"items":{"type":"object","required":["key","label","materials"],"properties":{"key":{"type":"string","maxLength":32},"label":{"type":"string","maxLength":80},"materials":{"type":"array","items":{"type":"string","enum":["apostila","scorm","quadro"]}}}}}}},"example":{"display_name":"Escola Exemplo","url":"https://escola.exemplo.com","open_url_template":"https://escola.exemplo.com/hotlearn/enviar?produto={product}&material={material}&acao={action}","actions":[{"key":"course","label":"Criar um curso novo","materials":["apostila"]},{"key":"scorm","label":"Criar uma aula SCORM","materials":["scorm"]}]}}}},"responses":{"200":{"description":"Conexão atualizada","content":{"application/json":{"example":{"data":{"id":7,"client":{"id":"01a13fdf-7c67-70b3-a3b7-bb60430403d8","name":"EstudioLMS"},"external_id":"42","display_name":"Escola Exemplo","url":"https://escola.exemplo.com","open_url_template":"https://escola.exemplo.com/hotlearn/enviar?produto={product}&material={material}&acao={action}","actions":[{"key":"course","label":"Criar um curso novo","materials":["apostila"]}],"scopes":["account:read","catalog:read","connection:write"],"authorized_by":{"name":"Ana Souza"},"authorized_at":"2026-10-15T14:02:11-03:00","last_used_at":"2026-10-16T09:40:00-03:00"}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Chave pessoal (sem conexão)"},"422":{"$ref":"#/components/responses/ValidationFailed"}}}},"/api/v1/login-links":{"post":{"operationId":"createLoginLink","tags":["account"],"summary":"Link de entrada já logado","description":"Gera um link que abre o HotLearn já logado, direto num destino: `studio` (painel), `library` (Meus produtos, ou os materiais de um produto com `product_id`), `material` (o editor da apostila) ou `scorm` (o editor do curso SCORM), com `material_id`.\n\n- Vale **60 segundos** e **uma vez só**: gere na hora do clique e redirecione a pessoa.\n- Se o e-mail for de quem já é **membro da conta**, a pessoa entra direto. Se não for, o link abre o login do HotLearn com o e-mail preenchido (ninguém entra sem senha) e, depois de entrar, leva ao destino. O **dono da conta** e os **administradores do HotLearn** nunca entram só pelo link: vão ao login com o e-mail preenchido e, depois de entrar, seguem ao destino. Se alguém da mesma conta já estiver logado naquele navegador, segue com a própria sessão; se for alguém de outra conta, a sessão é encerrada e o HotLearn pede o login.\n- Nos editores, a pessoa vê \"Voltar para {nome da conexão}\", que leva ao `url` da conexão.\n- Só aplicativo autorizado (com conexão). Pode consumir créditos da conta conforme a tabela de uso (`GET /usage`): a resposta cobrada traz `X-Credits-Charged` e `X-Credits-Balance`; sem saldo, `402 insufficient_credits`; acima do limite do dia, `429 daily_limit_reached`.\n- Limite: 30 chamadas por minuto.","x-translations":{"en":{"summary":"Already-signed-in link","description":"Creates a link that opens HotLearn already signed in, straight to a target: `studio` (panel), `library` (My products, or a product's materials with `product_id`), `material` (the workbook editor) or `scorm` (the SCORM course editor), with `material_id`.\n\n- Valid for **60 seconds** and **only once**: create it at click time and redirect the person.\n- If the email belongs to someone who is already an **account member**, they go straight in. If not, the link opens the HotLearn login with the email filled in (nobody gets in without a password) and, after signing in, goes to the target. The **account owner** and **HotLearn administrators** never sign in through the link alone: they go to the login with the email filled in and, after signing in, continue to the target. If someone from the same account is already signed in on that browser, they continue with their own session; if it is someone from another account, that session is ended and HotLearn asks for the login.\n- In the editors, the person sees \"Back to {connection name}\", which leads to the connection's `url`.\n- Authorized apps only (with a connection). May use account credits according to the usage table (`GET /usage`): a charged response carries `X-Credits-Charged` and `X-Credits-Balance`; without balance, `402 insufficient_credits`; over the daily limit, `429 daily_limit_reached`.\n- Limit: 30 calls per minute."},"es":{"summary":"Enlace de entrada con sesión iniciada","description":"Genera un enlace que abre HotLearn ya con la sesión iniciada, directo a un destino: `studio` (panel), `library` (Mis productos, o los materiales de un producto con `product_id`), `material` (el editor del cuadernillo) o `scorm` (el editor del curso SCORM), con `material_id`.\n\n- Vale **60 segundos** y **una sola vez**: genéralo en el momento del clic y redirige a la persona.\n- Si el correo es de alguien que ya es **miembro de la cuenta**, entra directo. Si no, el enlace abre el inicio de sesión de HotLearn con el correo completado (nadie entra sin contraseña) y, después de entrar, lleva al destino. El **dueño de la cuenta** y los **administradores de HotLearn** nunca entran solo con el enlace: van al inicio de sesión con el correo completado y, después de entrar, siguen al destino. Si alguien de la misma cuenta ya inició sesión en ese navegador, sigue con su propia sesión; si es alguien de otra cuenta, esa sesión se cierra y HotLearn pide el inicio de sesión.\n- En los editores, la persona ve \"Volver a {nombre de la conexión}\", que lleva al `url` de la conexión.\n- Solo aplicación autorizada (con conexión). Puede consumir créditos de la cuenta según la tabla de uso (`GET /usage`): la respuesta cobrada trae `X-Credits-Charged` y `X-Credits-Balance`; sin saldo, `402 insufficient_credits`; por encima del límite del día, `429 daily_limit_reached`.\n- Límite: 30 llamadas por minuto."}},"security":[{"oauth2":["login_links:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","target"],"properties":{"email":{"type":"string","format":"email"},"target":{"type":"string","enum":["studio","library","material","scorm"]},"product_id":{"type":"integer"},"material_id":{"type":"integer","description":"Obrigatório com `material` e `scorm`."}}},"example":{"email":"coordenacao@escola.exemplo.com","target":"material","material_id":161}}}},"responses":{"201":{"description":"Link criado","content":{"application/json":{"example":{"data":{"url":"https://hotlearn.com.br/login-link/Q2x8kWm4…","expires_in":60}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}},"422":{"$ref":"#/components/responses/ValidationFailed"}}}},"/api/v1/products":{"get":{"operationId":"listProducts","tags":["catalog"],"summary":"Listar produtos","description":"Acervo (padrão, no idioma pedido), biblioteca da conta (`collection=acquired`) ou criações da conta (`collection=created`). Em páginas, por cursor.\n\n- `owned`: a conta já adquiriu (ou é criação dela).\n- `exportable`: há matriz de conteúdo (a cópia personalizada, o original do acervo ou a da criação), que sempre exporta (PDF), ou um curso SCORM da conta sem erro. Quando falso, `export_block` traz `{code, message}`: `no_matrix` (nenhum material) ou `scorm_invalid` (só cursos SCORM, todos com erro na verificação). Exportável: `export_block: null`.\n- O filtro `exportable=true` é feito no banco e não roda a verificação do SCORM: um produto só com cursos SCORM com erro aparece nele, mas vem com `exportable: false` e `scorm_invalid`.\n\nCom a chave de aplicativo sem conta (`catalog:public`), só o acervo e só os campos públicos (sem `owned`, `exportable`, `credit_cost`, público e objetivo).","x-translations":{"en":{"summary":"List products","description":"Catalog (default, in the requested language), the account's library (`collection=acquired`) or the account's creations (`collection=created`). Paginated by cursor.\n\n- `owned`: the account already acquired it (or created it).\n- `exportable`: there is a content matrix (the customized copy, the catalog original or the creation's own), which always exports (PDF), or an account SCORM course without errors. When false, `export_block` carries `{code, message}`: `no_matrix` (no material at all) or `scorm_invalid` (only SCORM courses, all with verification errors). Exportable: `export_block: null`.\n- The `exportable=true` filter runs in the database and does not run the SCORM verification: a product whose only SCORM courses have errors shows up in it, but comes with `exportable: false` and `scorm_invalid`.\n\nWith an app key without an account (`catalog:public`), only the catalog and only public fields (no `owned`, `exportable`, `credit_cost`, audience and objective)."},"es":{"summary":"Listar productos","description":"Acervo (por defecto, en el idioma pedido), biblioteca de la cuenta (`collection=acquired`) o creaciones de la cuenta (`collection=created`). Por páginas, con cursor.\n\n- `owned`: la cuenta ya lo adquirió (o es creación suya).\n- `exportable`: hay matriz de contenido (la copia personalizada, el original del acervo o la de la creación), que siempre exporta (PDF), o un curso SCORM de la cuenta sin errores. Cuando es falso, `export_block` trae `{code, message}`: `no_matrix` (ningún material) o `scorm_invalid` (solo cursos SCORM, todos con errores en la verificación). Exportable: `export_block: null`.\n- El filtro `exportable=true` se hace en la base de datos y no ejecuta la verificación del SCORM: un producto solo con cursos SCORM con errores aparece en él, pero viene con `exportable: false` y `scorm_invalid`.\n\nCon la clave de aplicación sin cuenta (`catalog:public`), solo el acervo y solo los campos públicos (sin `owned`, `exportable`, `credit_cost`, público y objetivo)."}},"security":[{"oauth2":["catalog:read"]},{"personalKey":["catalog:read"]},{"oauth2":["catalog:public"]}],"x-example-query":"?collection=catalog&limit=12","parameters":[{"name":"collection","in":"query","required":false,"schema":{"type":"string","enum":["catalog","acquired","created"]},"description":"`catalog` (padrão), `acquired` ou `created`."},{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Busca no título, na descrição curta, no público e no objetivo (não busca no sumário)."},{"name":"category","in":"query","required":false,"schema":{"type":"string"},"description":"Categoria: `id` ou `slug`."},{"name":"level","in":"query","required":false,"schema":{"type":"string","enum":["iniciante","intermediario","avancado"]},"description":"`iniciante`, `intermediario` ou `avancado`."},{"name":"exportable","in":"query","required":false,"schema":{"type":"boolean"},"description":"`true` ou `false`. Feito no banco, sem a verificação do SCORM: confira o `exportable` de cada item."},{"name":"locale","in":"query","required":false,"schema":{"type":"string"},"description":"Idioma do acervo (`pt_BR`, `en`, `es`); também pelo `Accept-Language`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"De 1 a 50 (padrão 12)."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"O `next_cursor` da página anterior."}],"responses":{"200":{"description":"Página de produtos","content":{"application/json":{"example":{"data":[{"id":136,"slug":"arte-e-cultura","title":"Arte e Cultura","short_description":"Apostila para turmas do ensino médio.","locale":"pt_BR","category":{"id":5,"slug":"educacao-e-ead","name":"Educação e EAD"},"level":"iniciante","level_label":"Iniciante","workload":"30h","contents":["apostila","quiz_lms"],"cover_url":"https://hotlearn.com.br/storage/negocio-pronto/products/136/capa.jpg","published_at":"2026-10-08T14:38:56+00:00","origin":"catalog","audience":"Professores do ensino médio","objective":"Trabalhar arte e cultura com projetos.","credit_cost":1000,"version":"1.0","owned":true,"exportable":true,"export_block":null,"publications_count":1},{"id":141,"slug":"oficina-de-teatro","title":"Oficina de Teatro","short_description":"Curso SCORM para turmas do ensino médio.","locale":"pt_BR","category":{"id":5,"slug":"educacao-e-ead","name":"Educação e EAD"},"level":"iniciante","level_label":"Iniciante","workload":"30h","contents":[],"cover_url":"https://hotlearn.com.br/storage/negocio-pronto/products/141/capa.jpg","published_at":null,"origin":"created","audience":"Professores do ensino médio","objective":"Trabalhar arte e cultura com projetos.","credit_cost":0,"version":"1.0","owned":true,"exportable":false,"export_block":{"code":"scorm_invalid","message":"Os cursos SCORM deste produto têm erros e ainda não podem ser exportados. Corrija-os no HotLearn."},"publications_count":0}],"next_cursor":"eyJpZCI6MTM2LCJfcG9pbnRzVG9OZXh0SXRlbXMiOnRydWV9"}}}},"400":{"description":"O `cursor` informado é inválido. Recomece a lista sem ele.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Bad Request","status":400,"detail":"O `cursor` informado é inválido. Recomece a lista sem ele.","code":"invalid_cursor"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Dados inválidos.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Unprocessable Content","status":422,"detail":"Dados inválidos.","code":"validation_failed","errors":{"collection":["Use catalog, acquired ou created."]}}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/products/{product}":{"get":{"operationId":"getProduct","tags":["catalog"],"summary":"Detalhe do produto, com o sumário","description":"Tudo da lista mais o sumário (`toc`), a licença e os links de prévia e capa.\n\n`toc` são as seções da matriz, cortadas nos títulos principais: `kind` é `module`, `intro` (abertura curta) ou `appendix` (glossário, referências, gabarito…). `source` diz de onde veio: `personalizada` (a cópia que a conta editou), `original` (a do acervo) ou `criacao`. Ler pela API nunca cria a cópia personalizada.\n\nProduto de outra conta, ou fora do ar e não adquirido: `404`.\n\n`exportable` e `export_block` seguem a mesma regra da lista: `export_block` é `null` quando exporta; senão, `{code, message}` com `no_matrix` ou `scorm_invalid`.","x-translations":{"en":{"summary":"Product detail, with table of contents","description":"Everything in the list plus the table of contents (`toc`), the license and the preview and cover links.\n\n`toc` are the matrix sections, split at the main headings: `kind` is `module`, `intro` (short opening) or `appendix` (glossary, references, answer key…). `source` tells where it came from: `personalizada` (the copy the account edited), `original` (the catalog one) or `criacao` (creation). Reading through the API never creates the customized copy.\n\nAnother account's product, or one taken down and not acquired: `404`.\n\n`exportable` and `export_block` follow the same rule as the list: `export_block` is `null` when it exports; otherwise `{code, message}` with `no_matrix` or `scorm_invalid`."},"es":{"summary":"Detalle del producto, con el sumario","description":"Todo lo de la lista más el sumario (`toc`), la licencia y los enlaces de vista previa y portada.\n\n`toc` son las secciones de la matriz, cortadas en los títulos principales: `kind` es `module`, `intro` (apertura corta) o `appendix` (glosario, referencias, solucionario…). `source` indica de dónde vino: `personalizada` (la copia que la cuenta editó), `original` (la del acervo) o `criacao` (creación). Leer por la API nunca crea la copia personalizada.\n\nProducto de otra cuenta, o fuera de línea y no adquirido: `404`.\n\n`exportable` y `export_block` siguen la misma regla de la lista: `export_block` es `null` cuando exporta; si no, `{code, message}` con `no_matrix` o `scorm_invalid`."}},"security":[{"oauth2":["catalog:read"]},{"personalKey":["catalog:read"]}],"parameters":[{"name":"product","in":"path","required":true,"schema":{"type":"integer"},"example":136,"description":"Identificador do produto."}],"responses":{"200":{"description":"Produto","content":{"application/json":{"example":{"data":{"id":136,"slug":"arte-e-cultura","title":"Arte e Cultura","short_description":"Apostila para turmas do ensino médio.","locale":"pt_BR","category":{"id":5,"slug":"educacao-e-ead","name":"Educação e EAD"},"level":"iniciante","level_label":"Iniciante","workload":"30h","contents":["apostila","quiz_lms"],"cover_url":"https://hotlearn.com.br/storage/negocio-pronto/products/136/capa.jpg","published_at":"2026-10-08T14:38:56+00:00","origin":"catalog","audience":"Professores do ensino médio","objective":"Trabalhar arte e cultura com projetos.","credit_cost":1000,"version":"1.0","owned":true,"exportable":true,"export_block":null,"source":"personalizada","toc":[{"index":0,"title":"Como aproveitar esta apostila","kind":"intro","words":310,"est_bytes":4200},{"index":1,"title":"Capítulo 1 — Arte para quê?","kind":"module","words":1194,"est_bytes":14706},{"index":2,"title":"Glossário","kind":"appendix","words":420,"est_bytes":5120}],"license":{"id":1,"title":"Licença Comercial","version":"2.0"},"has_preview":true,"links":{"preview":"https://hotlearn.com.br/api/v1/products/136/preview","cover":"https://hotlearn.com.br/api/v1/products/136/cover","license":"https://hotlearn.com.br/api/v1/licenses/1"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}}}}},"/api/v1/products/{product}/preview":{"get":{"operationId":"getProductPreview","tags":["catalog"],"summary":"Prévia em PDF","description":"O PDF de amostra do produto, entregue direto (`application/pdf`). `404` quando o produto não tem prévia. Exige chave de conta com `catalog:read`: a vitrine pública (`catalog:public`, sem conta) tem só a lista, a capa e as categorias, sem prévia.","x-translations":{"en":{"summary":"PDF preview","description":"The product's sample PDF, delivered directly (`application/pdf`). `404` when the product has no preview. Requires an account key with `catalog:read`: the public showcase (`catalog:public`, no account) has only the list, the cover and the categories, no preview."},"es":{"summary":"Vista previa en PDF","description":"El PDF de muestra del producto, entregado directamente (`application/pdf`). `404` cuando el producto no tiene vista previa. Exige clave de cuenta con `catalog:read`: la vitrina pública (`catalog:public`, sin cuenta) tiene solo la lista, la portada y las categorías, sin vista previa."}},"security":[{"oauth2":["catalog:read"]},{"personalKey":["catalog:read"]}],"parameters":[{"name":"product","in":"path","required":true,"schema":{"type":"integer"},"example":136,"description":"Identificador do produto."}],"responses":{"200":{"description":"O arquivo PDF","content":{"application/pdf":[]}},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}}}}},"/api/v1/products/{product}/cover":{"get":{"operationId":"getProductCover","tags":["catalog"],"summary":"Capa","description":"A imagem da capa, entregue direto. Para mostrar numa página, o `cover_url` da lista já é um endereço público, sem chave.","x-translations":{"en":{"summary":"Cover","description":"The cover image, delivered directly. To show it on a page, the list's `cover_url` is already a public address, no key needed."},"es":{"summary":"Portada","description":"La imagen de la portada, entregada directamente. Para mostrarla en una página, el `cover_url` de la lista ya es una dirección pública, sin clave."}},"security":[{"oauth2":["catalog:read"]},{"personalKey":["catalog:read"]},{"oauth2":["catalog:public"]}],"parameters":[{"name":"product","in":"path","required":true,"schema":{"type":"integer"},"example":136,"description":"Identificador do produto."}],"responses":{"200":{"description":"A imagem","content":{"image/*":[]}},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}}}}},"/api/v1/categories":{"get":{"operationId":"listCategories","tags":["catalog"],"summary":"Categorias","description":"Todas as categorias, com quantos produtos do acervo publicado cada uma tem no idioma pedido. Lista curta: vem inteira, sem cursor.","x-translations":{"en":{"summary":"Categories","description":"All categories, with how many published catalog products each has in the requested language. Short list: returned whole, no cursor."},"es":{"summary":"Categorías","description":"Todas las categorías, con cuántos productos del acervo publicado tiene cada una en el idioma pedido. Lista corta: viene entera, sin cursor."}},"security":[{"oauth2":["catalog:read"]},{"personalKey":["catalog:read"]},{"oauth2":["catalog:public"]}],"responses":{"200":{"description":"Categorias","content":{"application/json":{"example":{"data":[{"id":5,"slug":"educacao-e-ead","name":"Educação e EAD","products_count":12}]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"}}}},"/api/v1/licenses/{license}":{"get":{"operationId":"getLicense","tags":["catalog"],"summary":"Licença comercial","description":"Título, versão e texto (HTML) da licença, para mostrar antes de adquirir. O `id` vem em `license` no detalhe do produto.","x-translations":{"en":{"summary":"Commercial license","description":"License title, version and text (HTML), to show before acquiring. The `id` comes in `license` in the product detail."},"es":{"summary":"Licencia comercial","description":"Título, versión y texto (HTML) de la licencia, para mostrar antes de adquirir. El `id` viene en `license` en el detalle del producto."}},"security":[{"oauth2":["catalog:read"]},{"personalKey":["catalog:read"]}],"parameters":[{"name":"license","in":"path","required":true,"schema":{"type":"integer"},"example":1,"description":"Identificador da licença."}],"responses":{"200":{"description":"Licença","content":{"application/json":{"example":{"data":{"id":1,"title":"Licença Comercial","version":"2.0","active":true,"body_html":"<h3>Licença Comercial</h3><p>…</p>"}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}}}}},"/api/v1/acquisitions":{"post":{"operationId":"createAcquisition","tags":["acquisitions"],"summary":"Adquirir um produto","description":"Desbloqueia o produto para a conta, com a mesma regra do botão do HotLearn: debita os créditos (o custo está em `credit_cost`), respeita o limite do período de teste, e plano ilimitado não debita.\n\n- **`Idempotency-Key` obrigatório.** Repetir a chamada com a mesma chave devolve a mesma resposta, sem cobrar de novo.\n- Produto já adquirido: `200` com `charged: false`, sem cobrar.\n- `license_version`: a versão da licença mostrada à pessoa ([`GET /licenses/{id}`](#op-getLicense)). Se mudou, `409 license_outdated` com a versão vigente.\n- `on_behalf_of`: quem clicou do lado do aplicativo (fica na auditoria do HotLearn).\n- Quem assina do lado do HotLearn é quem autorizou a conexão (ou, se essa pessoa saiu da conta, o dono). Sem nenhum dos dois: `409 connection_owner_missing`. Pode consumir créditos da conta conforme a tabela de uso (`GET /usage`): a resposta cobrada traz `X-Credits-Charged` e `X-Credits-Balance`; sem saldo, `402 insufficient_credits`; acima do limite do dia, `429 daily_limit_reached`.","x-translations":{"en":{"summary":"Acquire a product","description":"Unlocks the product for the account, with the same rule as the HotLearn button: debits credits (the cost is in `credit_cost`), respects the trial limit, and an unlimited plan does not debit.\n\n- **`Idempotency-Key` required.** Repeating the call with the same key returns the same response, without charging again.\n- Product already acquired: `200` with `charged: false`, no charge.\n- `license_version`: the license version shown to the person ([`GET /licenses/{id}`](#op-getLicense)). If it changed, `409 license_outdated` with the current version.\n- `on_behalf_of`: who clicked on the app side (kept in HotLearn's audit).\n- The HotLearn signer is whoever authorized the connection (or, if they left the account, the owner). With neither: `409 connection_owner_missing`. May use account credits according to the usage table (`GET /usage`): a charged response carries `X-Credits-Charged` and `X-Credits-Balance`; without balance, `402 insufficient_credits`; over the daily limit, `429 daily_limit_reached`."},"es":{"summary":"Adquirir un producto","description":"Desbloquea el producto para la cuenta, con la misma regla del botón de HotLearn: debita los créditos (el costo está en `credit_cost`), respeta el límite del período de prueba, y el plan ilimitado no debita.\n\n- **`Idempotency-Key` obligatorio.** Repetir la llamada con la misma clave devuelve la misma respuesta, sin cobrar de nuevo.\n- Producto ya adquirido: `200` con `charged: false`, sin cobrar.\n- `license_version`: la versión de la licencia mostrada a la persona ([`GET /licenses/{id}`](#op-getLicense)). Si cambió, `409 license_outdated` con la versión vigente.\n- `on_behalf_of`: quién hizo clic del lado de la aplicación (queda en la auditoría de HotLearn).\n- Quien firma del lado de HotLearn es quien autorizó la conexión (o, si esa persona salió de la cuenta, el dueño). Sin ninguno de los dos: `409 connection_owner_missing`. Puede consumir créditos de la cuenta según la tabla de uso (`GET /usage`): la respuesta cobrada trae `X-Credits-Charged` y `X-Credits-Balance`; sin saldo, `402 insufficient_credits`; por encima del límite del día, `429 daily_limit_reached`."}},"security":[{"oauth2":["acquisitions:write"]},{"personalKey":["acquisitions:write"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"},"description":"Obrigatório: um valor único por operação (ex.: um UUID). Sem ele, `400 idempotency_key_required`. A resposta (inclusive `4xx`) fica guardada por 24 horas e volta igual para a mesma chave; mesma chave com outros dados, `422 idempotency_key_reused`; chamada simultânea com a mesma chave, `409 idempotency_in_progress`. Use uma chave nova a cada nova tentativa (por exemplo, depois de corrigir o pedido)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["product_id"],"properties":{"product_id":{"type":"integer"},"license_version":{"type":"string","description":"Obrigatório quando o produto tem licença."},"on_behalf_of":{"type":"object","properties":{"external_user_id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","format":"email"}}}}},"example":{"product_id":136,"license_version":"2.0","on_behalf_of":{"external_user_id":"u-9","name":"Coordenação da Escola","email":"coordenacao@escola.exemplo.com"}}}}},"x-example-headers":{"Idempotency-Key":"6f1c2e8a-3b0d-4c55-9a7e-2f1d9b8c4a10"},"responses":{"201":{"description":"Adquirido agora","content":{"application/json":{"example":{"data":{"acquisition":{"id":58,"product":{"id":136,"slug":"arte-e-cultura","title":"Arte e Cultura"},"acquired_at":"2026-10-16T10:12:00-03:00","source":"plan_credit","credits_spent":2,"license_version":"2.0","acquired_by":{"name":"Ana Souza"},"via":{"app":"EstudioLMS","client_id":"01a13fdf-7c67-70b3-a3b7-bb60430403d8","on_behalf_of":{"external_user_id":"u-9","name":"Coordenação da Escola","email":"coordenacao@escola.exemplo.com"}}},"charged":true,"balance":{"credits":38,"unlimited":false}}}}}},"200":{"description":"Já era da conta (`charged: false`, nada cobrado)"},"400":{"description":"Envie o cabeçalho `Idempotency-Key` (um valor único por operação).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Bad Request","status":400,"detail":"Envie o cabeçalho `Idempotency-Key` (um valor único por operação).","code":"idempotency_key_required"}}}},"402":{"description":"A conta não tem créditos suficientes para este produto.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Payment Required","status":402,"detail":"A conta não tem créditos suficientes para este produto.","code":"insufficient_credits","required":5,"available":1}}}},"403":{"description":"A assinatura da conta não está ativa. Para adquirir, a conta precisa de assinatura ativa ou do período de teste.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Forbidden","status":403,"detail":"A assinatura da conta não está ativa. Para adquirir, a conta precisa de assinatura ativa ou do período de teste.","code":"subscription_inactive"}}}},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}},"409":{"description":"A licença mudou: a versão vigente é 2.0. Mostre a licença nova à pessoa e envie essa versão.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Conflict","status":409,"detail":"A licença mudou: a versão vigente é 2.0. Mostre a licença nova à pessoa e envie essa versão.","code":"license_outdated","license":{"id":1,"version":"2.0"}}}}},"422":{"$ref":"#/components/responses/ValidationFailed"}}},"get":{"operationId":"listAcquisitions","tags":["acquisitions"],"summary":"Histórico de aquisições","description":"Tudo o que a conta adquiriu, do mais recente para o mais antigo, pelo HotLearn ou por qualquer aplicativo: produto, data, origem (`plan_credit`, `trial`, `unlimited`, `admin`), créditos gastos, versão da licença e, quando veio de um aplicativo, qual (`via`). Filtro opcional `product_id`. Em páginas, por cursor.","x-translations":{"en":{"summary":"Acquisition history","description":"Everything the account acquired, newest first, through HotLearn or any app: product, date, source (`plan_credit`, `trial`, `unlimited`, `admin`), credits spent, license version and, when it came from an app, which one (`via`). Optional `product_id` filter. Paginated by cursor."},"es":{"summary":"Historial de adquisiciones","description":"Todo lo que la cuenta adquirió, del más reciente al más antiguo, por HotLearn o por cualquier aplicación: producto, fecha, origen (`plan_credit`, `trial`, `unlimited`, `admin`), créditos gastados, versión de la licencia y, cuando vino de una aplicación, cuál (`via`). Filtro opcional `product_id`. Por páginas, con cursor."}},"security":[{"oauth2":["acquisitions:read"]},{"personalKey":["acquisitions:read"]}],"x-example-query":"?limit=12","parameters":[{"name":"product_id","in":"query","required":false,"schema":{"type":"integer"},"description":"Só as aquisições deste produto."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"De 1 a 50 (padrão 12)."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"O `next_cursor` da página anterior."}],"responses":{"200":{"description":"Página de aquisições","content":{"application/json":{"example":{"data":[{"id":58,"product":{"id":136,"slug":"arte-e-cultura","title":"Arte e Cultura"},"acquired_at":"2026-10-16T10:12:00-03:00","source":"plan_credit","credits_spent":2,"license_version":"2.0","acquired_by":{"name":"Ana Souza"},"via":{"app":"EstudioLMS","client_id":"01a13fdf-7c67-70b3-a3b7-bb60430403d8","on_behalf_of":{"external_user_id":"u-9","name":"Coordenação da Escola","email":"coordenacao@escola.exemplo.com"}}}],"next_cursor":null}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/v1/products/{product}/materials":{"get":{"operationId":"listMaterials","tags":["materials"],"summary":"Materiais do produto","description":"O que a conta tem deste produto:\n\n- a **matriz**: a cópia que a conta personalizou (`source: personalizada`) ou, sem cópia, a original do acervo (`original`). Ler nunca cria a cópia;\n- o que a conta criou sobre o produto: outras matrizes, cursos SCORM, quadros (`criacao`).\n\n`formats` lista os arquivos que já dá para pedir. Curso SCORM traz `valid` e `errors` (com erro, o pacote não sai). Exige o produto adquirido (ou criação da conta) e assinatura ativa ou em teste.\n\nQuando a escola personaliza um produto do acervo, a matriz vira uma cópia com **outro `id`** (`source` passa de `original` a `personalizada`). Liste os materiais de novo em vez de guardar o `id`; as publicações antigas avisam com `outdated_reason: \"personalized\"` e `current_material_id`.","x-translations":{"en":{"summary":"Product materials","description":"What the account has of this product:\n\n- the **matrix**: the copy the account customized (`source: personalizada`) or, without a copy, the catalog original (`original`). Reading never creates the copy;\n- what the account created on the product: other matrices, SCORM courses, whiteboards (`criacao`).\n\n`formats` lists the files that can already be requested. A SCORM course has `valid` and `errors` (with errors, the package is not generated). Requires the product acquired (or created by the account) and an active or trial subscription.\n\nWhen the school customizes a catalog product, the matrix becomes a copy with **a new `id`** (`source` goes from `original` to `personalizada`). List the materials again instead of keeping the `id`; older publications warn with `outdated_reason: \"personalized\"` and `current_material_id`."},"es":{"summary":"Materiales del producto","description":"Lo que la cuenta tiene de este producto:\n\n- la **matriz**: la copia que la cuenta personalizó (`source: personalizada`) o, sin copia, la original del acervo (`original`). Leer nunca crea la copia;\n- lo que la cuenta creó sobre el producto: otras matrices, cursos SCORM, pizarras (`criacao`).\n\n`formats` lista los archivos que ya se pueden pedir. El curso SCORM trae `valid` y `errors` (con errores, el paquete no sale). Exige el producto adquirido (o creación de la cuenta) y suscripción activa o en prueba.\n\nCuando la escuela personaliza un producto del acervo, la matriz pasa a ser una copia con **otro `id`** (`source` pasa de `original` a `personalizada`). Lista los materiales de nuevo en vez de guardar el `id`; las publicaciones antiguas avisan con `outdated_reason: \"personalized\"` y `current_material_id`."}},"security":[{"oauth2":["materials:read"]},{"personalKey":["materials:read"]}],"parameters":[{"name":"product","in":"path","required":true,"schema":{"type":"integer"},"example":136,"description":"Identificador do produto."}],"responses":{"200":{"description":"Materiais","content":{"application/json":{"example":{"data":[{"id":161,"product_id":136,"type":"apostila","title":"Arte e Cultura","source":"personalizada","version":"1790850188","updated_at":"2026-10-08T14:03:08+00:00","formats":["pdf"],"exportable":true,"sections_count":26},{"id":162,"product_id":136,"type":"scorm","title":"Curso de Arte","source":"criacao","version":"1791390405","updated_at":"2026-10-14T20:06:45+00:00","formats":["scorm12","scorm2004","html5"],"exportable":true,"valid":true,"errors":[]}]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Forbidden","status":403,"detail":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","code":"not_acquired"}}}},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}}}}},"/api/v1/materials/{material}":{"get":{"operationId":"getMaterial","tags":["materials"],"summary":"Um material","description":"Dados do material, com as seções (capítulos) da matriz ou os avisos do curso SCORM. A resposta traz `ETag` com a versão: mande `If-None-Match` com ela e, se nada mudou, a resposta é `304`, sem corpo. A versão muda a cada salvamento.","x-translations":{"en":{"summary":"A material","description":"Material data, with the matrix sections (chapters) or the SCORM course warnings. The response carries `ETag` with the version: send it in `If-None-Match` and, if nothing changed, the response is `304`, with no body. The version changes on every save."},"es":{"summary":"Un material","description":"Datos del material, con las secciones (capítulos) de la matriz o los avisos del curso SCORM. La respuesta trae `ETag` con la versión: envíala en `If-None-Match` y, si nada cambió, la respuesta es `304`, sin cuerpo. La versión cambia en cada guardado."}},"security":[{"oauth2":["materials:read"]},{"personalKey":["materials:read"]}],"parameters":[{"name":"material","in":"path","required":true,"schema":{"type":"integer"},"example":162,"description":"Identificador do material."}],"responses":{"200":{"description":"Material","content":{"application/json":{"example":{"data":{"id":161,"product_id":136,"type":"apostila","title":"Arte e Cultura","source":"personalizada","version":"1790850188","updated_at":"2026-10-08T14:03:08+00:00","formats":["pdf"],"exportable":true,"sections_count":26,"sections":[{"index":0,"title":"Capítulo 1 — Arte para quê?","kind":"module","words":1194,"est_bytes":14706}]}}}}},"304":{"description":"Não mudou desde a versão do `If-None-Match`"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Forbidden","status":403,"detail":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","code":"not_acquired"}}}},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}}}}},"/api/v1/materials/{material}/content":{"get":{"operationId":"getMaterialContent","tags":["materials"],"summary":"Conteúdo da matriz","description":"A matriz inteira, no `format` pedido:\n\n- `html`: **HTML de lição**, para colar num editor de LMS. Só estrutura simples (títulos, parágrafos, listas, tabelas, citações, imagens) e cor em estilo dentro das próprias tags: caixas viram tabela de uma célula com fundo, que os editores (como o CKEditor 4) mantêm ao editar. Sem classes, sem `<style>`, sem script. As respostas das perguntas vão num bloco **Gabarito** no fim.\n- `markdown`: para IA, MCP e ferramentas de texto. Caixas viram citação; checklist vira `- [ ]`; gabarito no fim.\n- `json`: o documento do editor (o mesmo do \"Exportar JSON\" do HotLearn).\n\nRótulos (\"Gabarito\", \"Questão\", \"Atividade\") saem no idioma do material. `ETag` e `If-None-Match` como em [`GET /materials/{id}`](#op-getMaterial).","x-translations":{"en":{"summary":"Matrix content","description":"The whole matrix, in the requested `format`:\n\n- `html`: **lesson HTML**, to paste into an LMS editor. Only simple structure (headings, paragraphs, lists, tables, quotes, images) and color as inline style: boxes become one-cell tables with a background, which editors (such as CKEditor 4) keep when editing. No classes, no `<style>`, no script. Question answers go in an **Answer key** block at the end.\n- `markdown`: for AI, MCP and text tools. Boxes become quotes; checklists become `- [ ]`; answer key at the end.\n- `json`: the editor document (the same as HotLearn's \"Export JSON\").\n\nLabels (\"Answer key\", \"Question\", \"Activity\") come in the material's language. `ETag` and `If-None-Match` as in [`GET /materials/{id}`](#op-getMaterial)."},"es":{"summary":"Contenido de la matriz","description":"La matriz completa, en el `format` pedido:\n\n- `html`: **HTML de lección**, para pegar en un editor de LMS. Solo estructura simple (títulos, párrafos, listas, tablas, citas, imágenes) y color como estilo dentro de las propias etiquetas: las cajas se convierten en tablas de una celda con fondo, que los editores (como CKEditor 4) mantienen al editar. Sin clases, sin `<style>`, sin script. Las respuestas de las preguntas van en un bloque **Solucionario** al final.\n- `markdown`: para IA, MCP y herramientas de texto. Las cajas se convierten en citas; el checklist en `- [ ]`; solucionario al final.\n- `json`: el documento del editor (el mismo del \"Exportar JSON\" de HotLearn).\n\nLas etiquetas (\"Solucionario\", \"Pregunta\", \"Actividad\") salen en el idioma del material. `ETag` e `If-None-Match` como en [`GET /materials/{id}`](#op-getMaterial)."}},"security":[{"oauth2":["materials:read"]},{"personalKey":["materials:read"]}],"x-example-query":"?format=html","parameters":[{"name":"material","in":"path","required":true,"schema":{"type":"integer"},"example":161,"description":"Identificador da matriz."},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["html","markdown","json"]},"description":"`html` (padrão), `markdown` ou `json`."}],"responses":{"200":{"description":"Conteúdo","content":{"application/json":{"example":{"data":{"material_id":161,"version":"1790850188","source":"personalizada","format":"html","title":"Inglês para Viagem","content":"<h2>O cartão de embarque</h2><p>Boarding é quando o embarque começa…</p><table cellpadding=\"0\" cellspacing=\"0\" style=\"width:100%;border-collapse:collapse;margin:16px 0\"><tr><td style=\"background:#e8f3f5;border-left:4px solid #1d8a9c;padding:12px 16px\"><p><strong>Dica</strong></p><p>Confira o painel.</p></td></tr></table>…<h2>Gabarito</h2><ol style=\"list-style:none;padding-left:0\"><li>Questão 1. D) Até 22:35</li></ol>"}}}}},"304":{"description":"Não mudou"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Forbidden","status":403,"detail":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","code":"not_acquired"}}}},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}},"422":{"description":"Formato não disponível para este material. Use: html, markdown, json.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Unprocessable Content","status":422,"detail":"Formato não disponível para este material. Use: html, markdown, json.","code":"format_unavailable","formats":["html","markdown","json"]}}}}}}},"/api/v1/materials/{material}/sections/{section}":{"get":{"operationId":"getMaterialSection","tags":["materials"],"summary":"Um capítulo da matriz","description":"Uma seção do sumário (`index` de [`GET /materials/{id}`](#op-getMaterial)), em `html` ou `markdown`. O título do capítulo vem em `title`, **fora** do corpo: no LMS, ele vira o nome da lição. O gabarito das perguntas do capítulo vai no fim do corpo.","x-translations":{"en":{"summary":"One matrix chapter","description":"One table-of-contents section (`index` from [`GET /materials/{id}`](#op-getMaterial)), as `html` or `markdown`. The chapter title comes in `title`, **outside** the body: in the LMS it becomes the lesson name. The answer key of the chapter's questions goes at the end of the body."},"es":{"summary":"Un capítulo de la matriz","description":"Una sección del sumario (`index` de [`GET /materials/{id}`](#op-getMaterial)), en `html` o `markdown`. El título del capítulo viene en `title`, **fuera** del cuerpo: en el LMS se convierte en el nombre de la lección. El solucionario de las preguntas del capítulo va al final del cuerpo."}},"security":[{"oauth2":["materials:read"]},{"personalKey":["materials:read"]}],"x-example-query":"?format=html","parameters":[{"name":"material","in":"path","required":true,"schema":{"type":"integer"},"example":161,"description":"Identificador da matriz."},{"name":"section","in":"path","required":true,"schema":{"type":"integer"},"example":1,"description":"`index` da seção."},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["html","markdown"]},"description":"`html` (padrão) ou `markdown`."}],"responses":{"200":{"description":"Capítulo","content":{"application/json":{"example":{"data":{"material_id":161,"version":"1790850188","source":"personalizada","format":"html","title":"Módulo 2 — No aeroporto e a bordo","section":{"index":1,"title":"Módulo 2 — No aeroporto e a bordo","kind":"module","words":2210,"est_bytes":26003},"content":"<h2>O cartão de embarque</h2><p>Boarding é quando o embarque começa…</p><table cellpadding=\"0\" cellspacing=\"0\" style=\"width:100%;border-collapse:collapse;margin:16px 0\"><tr><td style=\"background:#e8f3f5;border-left:4px solid #1d8a9c;padding:12px 16px\"><p><strong>Dica</strong></p><p>Confira o painel.</p></td></tr></table>…<h2>Gabarito</h2><ol style=\"list-style:none;padding-left:0\"><li>Questão 1. D) Até 22:35</li></ol>"}}}}},"304":{"description":"Não mudou"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Forbidden","status":403,"detail":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","code":"not_acquired"}}}},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}},"422":{"description":"Formato não disponível para este material. Use: html, markdown, json.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Unprocessable Content","status":422,"detail":"Formato não disponível para este material. Use: html, markdown, json.","code":"format_unavailable","formats":["html","markdown","json"]}}}}}}},"/api/v1/materials/{material}/exports":{"post":{"operationId":"createExport","tags":["materials"],"summary":"Pedir um arquivo","description":"Pede o arquivo de um material num dos `formats` dele:\n\n- matriz: `pdf` — inteira, ou só um capítulo com `section` (o `index` do sumário; o PDF do capítulo sai sem capa, sumário e contracapa);\n- curso SCORM: `scorm12`, `scorm2004` ou `html5`.\n\n- Já há um arquivo pronto da mesma versão, formato e capítulo: `200` com o pedido `ready`.\n- Senão: `202` com o pedido `queued`. Acompanhe em [`GET /exports/{id}`](#op-getExport) até `ready` e baixe em [`/file`](#op-getExportFile).\n- Curso SCORM com erro: `422 scorm_invalid` com a lista em `errors` (corrigir no editor SCORM do HotLearn).\n- **`Idempotency-Key` obrigatório.** Pode consumir créditos da conta conforme a tabela de uso (`GET /usage`): a resposta cobrada traz `X-Credits-Charged` e `X-Credits-Balance`; sem saldo, `402 insufficient_credits`; acima do limite do dia, `429 daily_limit_reached`.","x-translations":{"en":{"summary":"Request a file","description":"Requests a material's file in one of its `formats`:\n\n- matrix: `pdf` — whole, or a single chapter with `section` (the table-of-contents `index`; the chapter PDF has no cover, table of contents or back cover);\n- SCORM course: `scorm12`, `scorm2004` or `html5`.\n\n- A ready file of the same version, format and chapter already exists: `200` with the request `ready`.\n- Otherwise: `202` with the request `queued`. Follow it at [`GET /exports/{id}`](#op-getExport) until `ready` and download from [`/file`](#op-getExportFile).\n- SCORM course with errors: `422 scorm_invalid` with the list in `errors` (fix it in the HotLearn SCORM editor).\n- **`Idempotency-Key` required.** May use account credits according to the usage table (`GET /usage`): a charged response carries `X-Credits-Charged` and `X-Credits-Balance`; without balance, `402 insufficient_credits`; over the daily limit, `429 daily_limit_reached`."},"es":{"summary":"Pedir un archivo","description":"Pide el archivo de un material en uno de sus `formats`:\n\n- matriz: `pdf` — completa, o solo un capítulo con `section` (el `index` del sumario; el PDF del capítulo sale sin portada, sumario ni contraportada);\n- curso SCORM: `scorm12`, `scorm2004` o `html5`.\n\n- Ya hay un archivo listo de la misma versión, formato y capítulo: `200` con la solicitud `ready`.\n- Si no: `202` con la solicitud `queued`. Síguela en [`GET /exports/{id}`](#op-getExport) hasta `ready` y descárgala en [`/file`](#op-getExportFile).\n- Curso SCORM con errores: `422 scorm_invalid` con la lista en `errors` (corregir en el editor SCORM de HotLearn).\n- **`Idempotency-Key` obligatorio.** Puede consumir créditos de la cuenta según la tabla de uso (`GET /usage`): la respuesta cobrada trae `X-Credits-Charged` y `X-Credits-Balance`; sin saldo, `402 insufficient_credits`; por encima del límite del día, `429 daily_limit_reached`."}},"security":[{"oauth2":["materials:read"]},{"personalKey":["materials:read"]}],"parameters":[{"name":"material","in":"path","required":true,"schema":{"type":"integer"},"example":162,"description":"Identificador do material."},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"},"description":"Obrigatório: um valor único por operação (ex.: um UUID). Sem ele, `400 idempotency_key_required`. A resposta (inclusive `4xx`) fica guardada por 24 horas e volta igual para a mesma chave; mesma chave com outros dados, `422 idempotency_key_reused`; chamada simultânea com a mesma chave, `409 idempotency_in_progress`. Use uma chave nova a cada nova tentativa (por exemplo, depois de corrigir o pedido)."}],"x-example-headers":{"Idempotency-Key":"6f1c2e8a-3b0d-4c55-9a7e-2f1d9b8c4a10"},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["format"],"properties":{"format":{"type":"string","enum":["pdf","scorm12","scorm2004","html5"]},"section":{"type":"integer","description":"Só com `pdf`: o capítulo (index do sumário)."}}},"example":{"format":"scorm12"}}}},"responses":{"202":{"description":"Pedido criado (`queued`)","content":{"application/json":{"example":{"data":{"id":31,"material_id":162,"format":"scorm12","section":null,"version":"1791390405","status":"queued","size":null,"expires_at":null,"error":null,"links":{"self":"https://hotlearn.com.br/api/v1/exports/31","file":null}}}}}},"200":{"description":"Já estava pronto (`ready`)","content":{"application/json":{"example":{"data":{"id":31,"material_id":162,"format":"scorm12","section":null,"version":"1791390405","status":"ready","size":98155,"expires_at":"2026-10-17T19:42:58+00:00","error":null,"links":{"self":"https://hotlearn.com.br/api/v1/exports/31","file":"https://hotlearn.com.br/api/v1/exports/31/file"}}}}}},"400":{"description":"Envie o cabeçalho `Idempotency-Key` (um valor único por operação).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Bad Request","status":400,"detail":"Envie o cabeçalho `Idempotency-Key` (um valor único por operação).","code":"idempotency_key_required"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Forbidden","status":403,"detail":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","code":"not_acquired"}}}},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}},"422":{"description":"O curso SCORM tem erros e não pode ser gerado. Corrija no editor SCORM do HotLearn.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Unprocessable Content","status":422,"detail":"O curso SCORM tem erros e não pode ser gerado. Corrija no editor SCORM do HotLearn.","code":"scorm_invalid","errors":[{"code":"no_screens","message":"O curso não tem telas."}]}}}}}}},"/api/v1/exports/{export}":{"get":{"operationId":"getExport","tags":["materials"],"summary":"Andamento do pedido","description":"`status`: `queued` (na fila), `running` (gerando), `ready` (pronto, com `size` e `expires_at`), `failed` (com `error`) ou `expired` (passou das 24 horas; peça de novo). Consulte a cada poucos segundos até sair de `queued`/`running`.","x-translations":{"en":{"summary":"Request status","description":"`status`: `queued`, `running`, `ready` (with `size` and `expires_at`), `failed` (with `error`) or `expired` (past 24 hours; request again). Poll every few seconds until it leaves `queued`/`running`."},"es":{"summary":"Estado de la solicitud","description":"`status`: `queued` (en cola), `running` (generando), `ready` (listo, con `size` y `expires_at`), `failed` (con `error`) o `expired` (pasaron 24 horas; pídelo de nuevo). Consulta cada pocos segundos hasta que salga de `queued`/`running`."}},"security":[{"oauth2":["materials:read"]},{"personalKey":["materials:read"]}],"parameters":[{"name":"export","in":"path","required":true,"schema":{"type":"integer"},"example":31,"description":"Identificador do pedido."}],"responses":{"200":{"description":"Pedido","content":{"application/json":{"example":{"data":{"id":31,"material_id":162,"format":"scorm12","section":null,"version":"1791390405","status":"ready","size":98155,"expires_at":"2026-10-17T19:42:58+00:00","error":null,"links":{"self":"https://hotlearn.com.br/api/v1/exports/31","file":"https://hotlearn.com.br/api/v1/exports/31/file"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}}}}},"/api/v1/exports/{export}/file":{"get":{"operationId":"getExportFile","tags":["materials"],"summary":"Baixar o arquivo","description":"O arquivo pronto (`application/pdf` ou `application/zip`). Fica disponível por 24 horas. Ainda não pronto (ou falhou): `409 export_not_ready`. Vencido: `410 export_expired`.","x-translations":{"en":{"summary":"Download the file","description":"The ready file (`application/pdf` or `application/zip`). Available for 24 hours. Not ready yet (or failed): `409 export_not_ready`. Expired: `410 export_expired`."},"es":{"summary":"Descargar el archivo","description":"El archivo listo (`application/pdf` o `application/zip`). Disponible por 24 horas. Aún no listo (o falló): `409 export_not_ready`. Vencido: `410 export_expired`."}},"security":[{"oauth2":["materials:read"]},{"personalKey":["materials:read"]}],"parameters":[{"name":"export","in":"path","required":true,"schema":{"type":"integer"},"example":31,"description":"Identificador do pedido."}],"responses":{"200":{"description":"O arquivo","content":{"application/pdf":[],"application/zip":[]}},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}},"409":{"description":"O arquivo ainda não está pronto. Consulte o pedido e tente de novo em instantes.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Conflict","status":"running","detail":"O arquivo ainda não está pronto. Consulte o pedido e tente de novo em instantes.","code":"export_not_ready"}}}},"410":{"description":"O arquivo venceu (fica 24 horas). Faça um pedido novo.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Gone","status":410,"detail":"O arquivo venceu (fica 24 horas). Faça um pedido novo.","code":"export_expired"}}}}}}},"/api/v1/products/{product}/quizzes":{"get":{"operationId":"listQuizzes","tags":["materials"],"summary":"Provas do produto","description":"A prova do produto, com o número de questões e os `formats` disponíveis: o CSV de cada plataforma que o produto traz (`csv-lms-estudio`, `csv-moodle`, `csv`) e sempre `json`. Mesmas regras de posse dos materiais.","x-translations":{"en":{"summary":"Product quizzes","description":"The product's quiz, with the number of questions and the available `formats`: the CSV for each platform the product includes (`csv-lms-estudio`, `csv-moodle`, `csv`) and always `json`. Same ownership rules as materials."},"es":{"summary":"Pruebas del producto","description":"La prueba del producto, con el número de preguntas y los `formats` disponibles: el CSV de cada plataforma que trae el producto (`csv-lms-estudio`, `csv-moodle`, `csv`) y siempre `json`. Mismas reglas de posesión de los materiales."}},"security":[{"oauth2":["materials:read"]},{"personalKey":["materials:read"]}],"parameters":[{"name":"product","in":"path","required":true,"schema":{"type":"integer"},"example":136,"description":"Identificador do produto."}],"responses":{"200":{"description":"Provas","content":{"application/json":{"example":{"data":[{"id":412,"product_id":136,"title":"Prova — CSV (LMS Estúdio)","questions_count":10,"formats":["csv-lms-estudio","csv-moodle","json"]}]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Forbidden","status":403,"detail":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","code":"not_acquired"}}}},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}}}}},"/api/v1/quizzes/{quiz}":{"get":{"operationId":"getQuiz","tags":["materials"],"summary":"Uma prova num formato","description":"- `csv-lms-estudio`, `csv-moodle`, `csv`: o arquivo **original** (`text/csv`), para importar com o importador da própria plataforma.\n- `json` (padrão): as perguntas lidas do CSV, com as alternativas e qual é a certa.","x-translations":{"en":{"summary":"A quiz in one format","description":"- `csv-lms-estudio`, `csv-moodle`, `csv`: the **original** file (`text/csv`), to import with the platform's own importer.\n- `json` (default): the questions read from the CSV, with the options and which one is correct."},"es":{"summary":"Una prueba en un formato","description":"- `csv-lms-estudio`, `csv-moodle`, `csv`: el archivo **original** (`text/csv`), para importar con el importador de la propia plataforma.\n- `json` (por defecto): las preguntas leídas del CSV, con las alternativas y cuál es la correcta."}},"security":[{"oauth2":["materials:read"]},{"personalKey":["materials:read"]}],"x-example-query":"?format=json","parameters":[{"name":"quiz","in":"path","required":true,"schema":{"type":"integer"},"example":412,"description":"`id` da prova (de `GET /products/{id}/quizzes`)."},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["json","csv-lms-estudio","csv-moodle","csv"]},"description":"`json` (padrão) ou um dos CSV."}],"responses":{"200":{"description":"A prova","content":{"application/json":{"example":{"data":{"id":412,"product_id":136,"title":"Prova — CSV (LMS Estúdio)","questions_count":10,"formats":["csv-lms-estudio","csv-moodle","json"],"questions":[{"text":"Qual o portão do voo?","points":10,"options":[{"text":"B12","correct":false},{"text":"B15","correct":true}]}]}}},"text/csv":[]}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Forbidden","status":403,"detail":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","code":"not_acquired"}}}},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}},"422":{"description":"Formato não disponível para este material. Use: csv-lms-estudio, csv-moodle, json.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Unprocessable Content","status":422,"detail":"Formato não disponível para este material. Use: csv-lms-estudio, csv-moodle, json.","code":"format_unavailable","formats":["csv-lms-estudio","csv-moodle","json"]}}}}}}},"/api/v1/publications":{"post":{"operationId":"createPublication","tags":["publications"],"summary":"Registrar uma publicação","description":"Conta ao HotLearn o que o aplicativo criou do lado dele a partir de um material: um curso, uma lição, uma aula SCORM. A publicação aparece para a escola na página **Integrações** do HotLearn.\n\n- Informe `material_id` (ou só `product_id`) e `kind` (`course`, `lesson`, `scorm_lesson`, `other`).\n- `material_version` é a versão usada (padrão: a atual). Quando a escola salvar o material de novo (ou personalizar o original do acervo), a publicação volta com `outdated: true` e o motivo em `outdated_reason` (`updated` ou `personalized`): é a deixa para oferecer a atualização.\n- `parts`: os capítulos enviados (`index` do sumário), cada um com a referência do seu lado.\n- `external_ref` e `external_url`: como achar o item do seu lado.\n- Só aplicativo autorizado (com conexão). **`Idempotency-Key` obrigatório.** Pode consumir créditos da conta conforme a tabela de uso (`GET /usage`): a resposta cobrada traz `X-Credits-Charged` e `X-Credits-Balance`; sem saldo, `402 insufficient_credits`; acima do limite do dia, `429 daily_limit_reached`.","x-translations":{"en":{"summary":"Record a publication","description":"Tells HotLearn what the app created on its side from a material: a course, a lesson, a SCORM lesson. The publication shows up for the school on HotLearn's **Integrations** page.\n\n- Send `material_id` (or just `product_id`) and `kind` (`course`, `lesson`, `scorm_lesson`, `other`).\n- `material_version` is the version used (default: the current one). When the school saves the material again (or customizes the catalog original), the publication comes back with `outdated: true` and the reason in `outdated_reason` (`updated` or `personalized`): the cue to offer an update.\n- `parts`: the chapters sent (table-of-contents `index`), each with its reference on your side.\n- `external_ref` and `external_url`: how to find the item on your side.\n- Authorized apps only (with a connection). **`Idempotency-Key` required.** May use account credits according to the usage table (`GET /usage`): a charged response carries `X-Credits-Charged` and `X-Credits-Balance`; without balance, `402 insufficient_credits`; over the daily limit, `429 daily_limit_reached`."},"es":{"summary":"Registrar una publicación","description":"Le cuenta a HotLearn lo que la aplicación creó de su lado a partir de un material: un curso, una lección, una clase SCORM. La publicación aparece para la escuela en la página **Integraciones** de HotLearn.\n\n- Informa `material_id` (o solo `product_id`) y `kind` (`course`, `lesson`, `scorm_lesson`, `other`).\n- `material_version` es la versión usada (por defecto: la actual). Cuando la escuela guarde el material de nuevo (o personalice el original del acervo), la publicación vuelve con `outdated: true` y el motivo en `outdated_reason` (`updated` o `personalized`): es la señal para ofrecer la actualización.\n- `parts`: los capítulos enviados (`index` del sumario), cada uno con su referencia de tu lado.\n- `external_ref` y `external_url`: cómo encontrar el elemento de tu lado.\n- Solo aplicación autorizada (con conexión). **`Idempotency-Key` obligatorio.** Puede consumir créditos de la cuenta según la tabla de uso (`GET /usage`): la respuesta cobrada trae `X-Credits-Charged` y `X-Credits-Balance`; sin saldo, `402 insufficient_credits`; por encima del límite del día, `429 daily_limit_reached`."}},"security":[{"oauth2":["publications:write"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"},"description":"Obrigatório: um valor único por operação (ex.: um UUID). Sem ele, `400 idempotency_key_required`. A resposta (inclusive `4xx`) fica guardada por 24 horas e volta igual para a mesma chave; mesma chave com outros dados, `422 idempotency_key_reused`; chamada simultânea com a mesma chave, `409 idempotency_in_progress`. Use uma chave nova a cada nova tentativa (por exemplo, depois de corrigir o pedido)."}],"x-example-headers":{"Idempotency-Key":"6f1c2e8a-3b0d-4c55-9a7e-2f1d9b8c4a10"},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["kind"],"properties":{"material_id":{"type":"integer"},"product_id":{"type":"integer"},"material_version":{"type":"string"},"kind":{"type":"string","enum":["course","lesson","scorm_lesson","other"]},"external_ref":{"type":"string"},"external_url":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"string","enum":["draft","published","archived"]},"parts":{"type":"array","items":{"type":"object","required":["index"],"properties":{"index":{"type":"integer"},"title":{"type":"string"},"external_ref":{"type":"string"}}}},"on_behalf_of":{"type":"object","properties":{"external_user_id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","format":"email"}}}}},"example":{"material_id":161,"kind":"course","title":"Inglês para Viagem — Turma A","external_ref":"curso-77","external_url":"https://escola.lmsestudio.com/cursos/77","parts":[{"index":1,"title":"Módulo 2 — No aeroporto","external_ref":"licao-301"}],"on_behalf_of":{"external_user_id":"u-9","name":"Coordenação da Escola"}}}}},"responses":{"201":{"description":"Registrada","content":{"application/json":{"example":{"data":{"id":15,"product":{"id":136,"title":"Inglês para Viagem"},"material_id":161,"current_material_id":161,"material_version":"1790850188","outdated":false,"outdated_reason":null,"kind":"course","external_ref":"curso-77","external_url":"https://escola.lmsestudio.com/cursos/77","title":"Inglês para Viagem — Turma A","status":"published","parts":[{"index":1,"title":"Módulo 2 — No aeroporto","external_ref":"licao-301"}],"on_behalf_of":{"external_user_id":"u-9","name":"Coordenação da Escola"},"created_at":"2026-10-16T10:30:00-03:00","updated_at":"2026-10-16T10:30:00-03:00"}}}}},"400":{"description":"Envie o cabeçalho `Idempotency-Key` (um valor único por operação).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Bad Request","status":400,"detail":"Envie o cabeçalho `Idempotency-Key` (um valor único por operação).","code":"idempotency_key_required"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Forbidden","status":403,"detail":"A conta ainda não adquiriu este produto. Adquira para ler os materiais.","code":"not_acquired"}}}},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}},"422":{"$ref":"#/components/responses/ValidationFailed"}}},"get":{"operationId":"listPublications","tags":["publications"],"summary":"Publicações do aplicativo","description":"As publicações que **este aplicativo** registrou nesta conta (de todas as conexões dele), da mais recente para a mais antiga. Filtros `product_id`, `material_id` e `status`. Em páginas, por cursor. Use `outdated` para saber o que mudou no HotLearn depois de publicado. Com chave pessoal (do dono), a lista traz as publicações de todos os aplicativos da conta, só para leitura.\n\nCada publicação traz `outdated`, `outdated_reason` e `current_material_id`:\n\n- em dia: `outdated: false`, `outdated_reason: null` e `current_material_id` igual a `material_id`;\n- `updated`: a escola salvou o material de novo (a versão mudou);\n- `personalized`: a publicação saiu do original do acervo e a escola personalizou depois; a matriz dela agora é a cópia, com outro id (`current_material_id`).","x-translations":{"en":{"summary":"App publications","description":"The publications **this app** recorded in this account (across all its connections), newest first. Filters `product_id`, `material_id` and `status`. Paginated by cursor. Use `outdated` to know what changed in HotLearn after publishing. With a personal key (the owner's), the list shows the publications of every app in the account, read-only.\n\nEach publication carries `outdated`, `outdated_reason` and `current_material_id`:\n\n- up to date: `outdated: false`, `outdated_reason: null` and `current_material_id` equal to `material_id`;\n- `updated`: the school saved the material again (the version changed);\n- `personalized`: the publication came from the catalog original and the school customized it later; its matrix is now the copy, with another id (`current_material_id`)."},"es":{"summary":"Publicaciones de la aplicación","description":"Las publicaciones que **esta aplicación** registró en esta cuenta (de todas sus conexiones), de la más reciente a la más antigua. Filtros `product_id`, `material_id` y `status`. Por páginas, con cursor. Usa `outdated` para saber qué cambió en HotLearn después de publicar. Con clave personal (del dueño), la lista trae las publicaciones de todas las aplicaciones de la cuenta, solo para lectura.\n\nCada publicación trae `outdated`, `outdated_reason` y `current_material_id`:\n\n- al día: `outdated: false`, `outdated_reason: null` y `current_material_id` igual a `material_id`;\n- `updated`: la escuela guardó el material de nuevo (la versión cambió);\n- `personalized`: la publicación salió del original del acervo y la escuela lo personalizó después; su matriz ahora es la copia, con otro id (`current_material_id`)."}},"security":[{"oauth2":["publications:read"]},{"personalKey":["publications:read"]}],"x-example-query":"?status=published","parameters":[{"name":"product_id","in":"query","required":false,"schema":{"type":"integer"},"description":"Só deste produto."},{"name":"material_id","in":"query","required":false,"schema":{"type":"integer"},"description":"Só deste material."},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["draft","published","archived"]},"description":"Situação."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"De 1 a 50 (padrão 12)."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"O `next_cursor` da página anterior."}],"responses":{"200":{"description":"Publicações","content":{"application/json":{"example":{"data":[{"id":15,"product":{"id":136,"title":"Inglês para Viagem"},"material_id":161,"current_material_id":161,"material_version":"1790850188","outdated":false,"outdated_reason":null,"kind":"course","external_ref":"curso-77","external_url":"https://escola.lmsestudio.com/cursos/77","title":"Inglês para Viagem — Turma A","status":"published","parts":[{"index":1,"title":"Módulo 2 — No aeroporto","external_ref":"licao-301"}],"on_behalf_of":{"external_user_id":"u-9","name":"Coordenação da Escola"},"created_at":"2026-10-16T10:30:00-03:00","updated_at":"2026-10-16T10:30:00-03:00"},{"id":12,"product":{"id":136,"title":"Inglês para Viagem"},"material_id":158,"current_material_id":161,"material_version":"1789000000","outdated":true,"outdated_reason":"personalized","kind":"course","external_ref":"curso-70","external_url":"https://escola.lmsestudio.com/cursos/70","title":"Inglês para Viagem — Turma piloto","status":"published","parts":[],"on_behalf_of":{"external_user_id":"u-9","name":"Coordenação da Escola"},"created_at":"2026-10-10T09:00:00-03:00","updated_at":"2026-10-10T09:00:00-03:00"}],"next_cursor":null}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/v1/publications/{publication}":{"patch":{"operationId":"updatePublication","tags":["publications"],"summary":"Atualizar uma publicação","description":"Muda `title`, `status` (`draft`, `published`, `archived`) ou `external_url`. Só as publicações deste aplicativo; de outro, `404`. Só esses três campos mudam (`parts` não). Não há `DELETE`: quando o curso for apagado do seu lado, mande `PATCH` com `status: archived`.","x-translations":{"en":{"summary":"Update a publication","description":"Changes `title`, `status` (`draft`, `published`, `archived`) or `external_url`. Only this app's publications; another app's → `404`. Only these three fields change (`parts` does not). There is no `DELETE`: when the course is deleted on your side, send `PATCH` with `status: archived`."},"es":{"summary":"Actualizar una publicación","description":"Cambia `title`, `status` (`draft`, `published`, `archived`) o `external_url`. Solo las publicaciones de esta aplicación; de otra, `404`. Solo cambian esos tres campos (`parts` no). No hay `DELETE`: cuando el curso se borre de tu lado, envía `PATCH` con `status: archived`."}},"security":[{"oauth2":["publications:write"]}],"parameters":[{"name":"publication","in":"path","required":true,"schema":{"type":"integer"},"example":15,"description":"Identificador da publicação."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"status":{"type":"string","enum":["draft","published","archived"]},"external_url":{"type":["string","null"],"format":"uri"}}},"example":{"status":"archived"}}}},"responses":{"200":{"description":"Atualizada","content":{"application/json":{"example":{"data":{"id":15,"product":{"id":136,"title":"Inglês para Viagem"},"material_id":161,"current_material_id":161,"material_version":"1790850188","outdated":false,"outdated_reason":null,"kind":"course","external_ref":"curso-77","external_url":"https://escola.lmsestudio.com/cursos/77","title":"Inglês para Viagem — Turma A","status":"archived","parts":[{"index":1,"title":"Módulo 2 — No aeroporto","external_ref":"licao-301"}],"on_behalf_of":{"external_user_id":"u-9","name":"Coordenação da Escola"},"created_at":"2026-10-16T10:30:00-03:00","updated_at":"2026-10-16T10:30:00-03:00"}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"description":"Não encontrado.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Not Found","status":404,"detail":"Não encontrado.","code":"not_found"}}}},"422":{"$ref":"#/components/responses/ValidationFailed"}}}},"/api/v1/openapi.json":{"get":{"operationId":"openapi","tags":["docs"],"summary":"Esta especificação (OpenAPI 3.1)","description":"Para gerar clientes ou importar no Postman/Insomnia. Limite: 60 chamadas por minuto.","x-translations":{"en":{"summary":"This specification (OpenAPI 3.1)","description":"To generate clients or import into Postman/Insomnia. Limit: 60 calls per minute."},"es":{"summary":"Esta especificación (OpenAPI 3.1)","description":"Para generar clientes o importar en Postman/Insomnia. Límite: 60 llamadas por minuto."}},"security":[],"responses":{"200":{"description":"Especificação","content":{"application/json":[]}}}}}},"components":{"securitySchemes":{"oauth2":{"type":"oauth2","description":"Aplicativos (LMS, MCP, parceiros): código de autorização com PKCE (S256), chave de 1 h e renovação rotativa.","flows":{"authorizationCode":{"authorizationUrl":"https://hotlearn.com.br/oauth/authorize","tokenUrl":"https://hotlearn.com.br/oauth/token","refreshUrl":"https://hotlearn.com.br/oauth/token","scopes":{"account:read":"Ver o nome e o e-mail, o plano e o saldo de créditos","catalog:read":"Ver o acervo, os produtos adquiridos e as criações","acquisitions:read":"Ver o histórico de aquisições","acquisitions:write":"Adquirir produtos com os créditos da conta","materials:read":"Ler e baixar os materiais da conta","publications:read":"Ver as publicações deste aplicativo","publications:write":"Registrar o que foi criado a partir dos materiais","connection:write":"Atualizar os dados da própria conexão","login_links:write":"Abrir o HotLearn já logado (só membros da conta)"}},"clientCredentials":{"tokenUrl":"https://hotlearn.com.br/oauth/token","scopes":{"catalog:public":"Vitrine pública do acervo (aplicativo sem conta)"}}}},"personalKey":{"type":"http","scheme":"bearer","description":"Chave pessoal criada pelo dono da conta em Integrações (scripts, assistentes de IA). Mesmo cabeçalho: `Authorization: Bearer {chave}`."}},"schemas":{"Problem":{"type":"object","description":"Erro no formato `application/problem+json` (RFC 9457). Decida pelo `code`, que é estável; o `detail` vem no idioma pedido.","required":["type","title","status","detail","code"],"properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"code":{"type":"string"},"errors":{"type":"object","description":"Só em `validation_failed`: mensagens por campo."},"scopes":{"type":"array","items":{"type":"string"},"description":"Só em `insufficient_scope`: o que faltou."}}}},"responses":{"Unauthenticated":{"description":"Chave ausente, inválida, vencida ou de conexão desfeita. `unauthenticated`: renove uma vez com a chave de renovação. `connection_revoked`: não renove; a escola desconectou, peça para conectar de novo.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Chave ausente, inválida ou vencida. Envie `Authorization: Bearer {chave}` ou renove a chave.","code":"unauthenticated"}}}},"Forbidden":{"description":"Falta o escopo da rota (`insufficient_scope`) ou a conta está inativa (`account_inactive`)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Forbidden","status":403,"detail":"A chave não tem a permissão necessária: account:read.","code":"insufficient_scope","scopes":["account:read"]}}}},"ValidationFailed":{"description":"Dados inválidos (`validation_failed`)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Unprocessable Content","status":422,"detail":"Dados inválidos.","code":"validation_failed","errors":{"open_url_template":["Precisa ser do mesmo domínio de `url`."]}}}}},"RateLimited":{"description":"Limite de chamadas estourado (`rate_limited`); veja `Retry-After`. Mais de 300 respostas `401` sem chave válida por minuto, do mesmo endereço de rede, também bloqueiam esse endereço com `429`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"about:blank","title":"Too Many Requests","status":429,"detail":"Muitas chamadas. Tente de novo depois do tempo indicado em `Retry-After`.","code":"rate_limited"}}}}}}}