> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gravitypay.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Área de membros

> Leia cursos, turmas, alunos, matrículas e progresso da sua área de membros pela API.

A API lê a área de membros da loja da chave: cursos com módulos e aulas, turmas, alunos com as
matrículas e o progresso de cada aluno. Com isso o seu sistema monta o catálogo, cruza alunos com o
seu cadastro e acompanha a formação. A API só lê: matricular, revogar e mover de turma continuam
no painel.

## Escopos

| Dado                            | Escopo          |
| ------------------------------- | --------------- |
| Cursos, módulos, aulas e turmas | `courses:read`  |
| Alunos, matrículas e progresso  | `students:read` |

Chaves criadas antes dos escopos não têm nenhum dos dois. Marque-os na chave, no painel. Veja
[Autenticação](/authentication).

## Modelo

### Curso

Um curso pertence a um produto, e cada produto tem no máximo um curso. O curso tem **módulos**, e
cada módulo tem **aulas**, sempre em ordem de `position`. Rascunhos aparecem, com
`status: "DRAFT"`, porque a integração é da própria loja.

A aula traz a estrutura: título, tipo (`VIDEO`, `TEXT` ou `QUIZ`), status, regra de liberação e
duração do vídeo. O conteúdo pago nunca sai pela API: nada de vídeo, texto, quiz, materiais ou
transcrição.

Referência: **Cursos → Listar cursos** e **Cursos → Consultar curso**.

### Turma

Todo curso tem uma **turma padrão** (`isDefault: true`) e pode ter outras. O módulo diz quais
turmas o veem em `visibleCohortIds`: `null` significa todas as turmas.

Referência: **Turmas → Listar turmas**. As turmas também vêm dentro de **Consultar curso**.

### Aluno

O aluno é a pessoa com acesso à área de membros da loja. Um aluno sem matrícula continua
aparecendo, com `enrollments` vazio. A conta de pré-visualização que o seller usa para ver a área
nunca aparece.

Referência: **Alunos → Listar alunos** e **Alunos → Consultar aluno**.

### Matrícula

A matrícula liga o aluno a um curso e a **uma turma**. Ela tem dois campos que respondem perguntas
diferentes:

* `status` é o estado gravado: `ACTIVE` ou `REVOKED` (com `revokeReason`).
* `hasAccess` diz se o aluno entra agora: matrícula `ACTIVE` e `expiresAt` nulo ou futuro. Uma
  matrícula `ACTIVE` com acesso vencido vem com `hasAccess: false`.

`guaranteeEndsAt` é o fim da trava de garantia: até essa data, o conteúdo marcado para depois da
garantia fica travado para o aluno.

### Progresso

O progresso é calculado por matrícula, na hora da leitura.

* `totalLessons` conta só as aulas **publicadas**, de módulos **publicados**, que a **turma da
  matrícula** vê. Aula em rascunho ou de módulo oculto para a turma fica fora da conta e volta
  quando fica visível de novo.
* `percent` é `completedLessons / totalLessons × 100`, arredondado, e `0` quando não há aulas.
  É o mesmo percentual que o aluno vê na área de membros.
* `completedAt` é preenchido quando todas as aulas contadas estão concluídas. Uma aula nova
  publicada tira o 100%.
* `lessons` lista cada aula contada, em ordem. A aula que o aluno não abriu vem `NOT_STARTED`.
* Matrícula revogada ou vencida mantém o progresso gravado, e `certificate` continua preenchido
  quando o certificado foi emitido.

Referência: **Alunos → Progresso do aluno**.

<Warning>
  **Chave de teste lê dados reais.** As chaves `gp_test_` e `gp_live_` devolvem os mesmos alunos e
  o mesmo progresso da loja. Proteja a chave de teste com o mesmo cuidado.
</Warning>

## Sincronizando alunos e progresso

As listas usam `page` e `perPage` (de 1 a 100, padrão 50) e respondem com `hasMore`. Pagine até
`hasMore` ser `false`. A ordem é estável (`createdAt` decrescente e, no empate, `id`), então a
paginação não pula alunos.

```js Node.js theme={null}
const API = "https://api.gravitypay.app";
const headers = { Authorization: `Bearer ${process.env.GRAVITYPAY_API_KEY}` };

async function get(path) {
  const res = await fetch(`${API}${path}`, { headers });
  if (!res.ok) {
    const { error } = await res.json();
    throw new Error(`${res.status} ${error.type}: ${error.message}`);
  }
  return res.json();
}

// 1. Todos os alunos, com as matrículas e a turma de cada uma.
const alunos = [];
for (let page = 1; ; page++) {
  const lista = await get(`/v1/students?page=${page}&perPage=100`);
  alunos.push(...lista.data);
  if (!lista.hasMore) break;
}

// 2. O progresso de cada aluno com matrícula.
for (const aluno of alunos) {
  if (aluno.enrollments.length === 0) continue;

  const { data } = await get(`/v1/students/${aluno.id}/progress`);
  for (const progresso of data) {
    console.log(aluno.email, progresso.courseId, `${progresso.percent}%`);
  }
}
```

Para acompanhar só uma turma, filtre a lista com `cohortId`. Os filtros de matrícula (`courseId`,
`cohortId` e `enrollmentStatus`) escolhem os alunos, mas cada aluno continua com todas as
matrículas em `enrollments`.

## Erros

| Status | `error.type`         | Quando                                                                                                          |
| ------ | -------------------- | --------------------------------------------------------------------------------------------------------------- |
| `400`  | `invalid_request`    | `page`, `perPage` ou filtro inválido, ou `courseId`/`cohortId` de outra loja. `error.param` diz qual parâmetro. |
| `401`  | `authentication`     | Chave ausente, inválida ou revogada.                                                                            |
| `403`  | `insufficient_scope` | A chave não tem o escopo. `error.requiredScope` diz qual.                                                       |
| `404`  | `not_found`          | Curso ou aluno inexistente, de outra loja ou de pré-visualização.                                               |
