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

# Ajustar pontos de um aluno

> Dá ou tira pontos, com o motivo que o aluno lê.



## OpenAPI

````yaml POST /v1/clubs/{clubId}/students/{studentId}/points
openapi: 3.0.3
info:
  title: weve API
  description: >
    Contrato entre a API Go e o dashboard. **Este arquivo é a fonte de
    verdade**:

    os tipos e a interface de servidor em Go, e o client TypeScript, são todos

    gerados daqui (`make openapi`). Handler que divergir da interface não
    compila.


    A superfície interna (`/internal/*`) fica de fora de propósito: é acesso de

    máquina, protegido por segredo dedicado, e não é contrato de cliente.
  version: 0.2.0
servers:
  - url: https://api.useweve.com
    description: produção
  - url: http://localhost:8080
    description: desenvolvimento
security: []
tags:
  - name: health
  - name: session
  - name: account
  - name: clubs
  - name: members
  - name: webhooks
  - name: outgoing-webhooks
  - name: sales-platforms
  - name: catalog
  - name: content
  - name: classroom
  - name: community
  - name: mentorships
  - name: agents
  - name: lives
  - name: students
  - name: auth
  - name: campaigns
  - name: workflows
  - name: email
  - name: exports
  - name: certificates
  - name: terms
  - name: mcp
  - name: gamification
  - name: support
  - name: help
paths:
  /v1/clubs/{clubId}/students/{studentId}/points:
    parameters:
      - $ref: '#/components/parameters/OrgId'
      - $ref: '#/components/parameters/StudentId'
    post:
      tags:
        - gamification
      summary: Dá ou tira pontos de um aluno
      description: |
        Um ajuste à mão, com o motivo — que o aluno lê no extrato dele. Tirar
        não deixa o total abaixo de zero (422 `points_below_zero`). Vale com a
        gamificação desligada também: é a equipe quem decide.

        Exige permissão de administração do club.
      operationId: adjustStudentPoints
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PointAdjustment'
      responses:
        '201':
          description: Os pontos do aluno depois do ajuste
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StudentPointsResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalError'
      security:
        - userSession: []
components:
  parameters:
    OrgId:
      name: clubId
      in: path
      required: true
      description: >
        Id público da organização — o mesmo que `GET /v1/me` devolve em

        `organization_id`.


        Na URL o recurso se chama **club**; no contrato e no domínio,
        organization.

        A divergência é deliberada: `clubs` é a palavra do produto, e a URL é o
        que

        as pessoas leem.


        É o identificador do provedor de autenticação, e é assim de propósito:

        o cliente precisa nomear a organização ao pedir o token, e o token é o

        que prova o escopo. Um id só nosso obrigaria a traduzir um no outro

        antes de ter um token — e a tradução exigiria uma chamada escopada, que

        é justamente a que ainda não dá para fazer.


        O uuid interno da organização não aparece no contrato: ele é o que as

        chaves estrangeiras do domínio referenciam, e continua sendo nosso.
      schema:
        type: string
        format: uuid
    StudentId:
      name: studentId
      in: path
      required: true
      schema:
        type: string
        format: uuid
  schemas:
    PointAdjustment:
      type: object
      required:
        - points
        - note
      properties:
        points:
          description: Positivo dá, negativo tira. Nunca zero.
          type: integer
          minimum: -10000
          maximum: 10000
        note:
          description: O motivo, que o aluno lê no extrato.
          type: string
          minLength: 1
          maxLength: 140
    StudentPointsResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/StudentPoints'
    StudentPoints:
      type: object
      required:
        - total
        - week
        - level
        - entries
        - entries_total
      properties:
        total:
          type: integer
        week:
          description: Os pontos dos últimos sete dias.
          type: integer
        level:
          allOf:
            - $ref: '#/components/schemas/LevelProgress'
          nullable: true
          description: Nulo quando o club nunca ligou a gamificação.
        entries:
          type: array
          items:
            $ref: '#/components/schemas/PointEntry'
        entries_total:
          type: integer
    ErrorEnvelope:
      type: object
      description: >-
        Envelope único de erro da API. O `code` é o contrato com o cliente — a
        `message` é diagnóstico, e pode mudar.
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
          properties:
            code:
              type: string
            message:
              type: string
    LevelProgress:
      description: Onde o aluno está na escada.
      type: object
      required:
        - level
        - level_number
        - levels_count
        - next
        - points_to_next
        - progress
      properties:
        level:
          $ref: '#/components/schemas/GamificationLevel'
        level_number:
          description: A posição do nível na escada, a partir de 1.
          type: integer
        levels_count:
          type: integer
        next:
          allOf:
            - $ref: '#/components/schemas/GamificationLevel'
          nullable: true
          description: O próximo degrau; nulo no último.
        points_to_next:
          description: Quantos pontos faltam para o próximo; zero no último.
          type: integer
        progress:
          description: >-
            A fração do caminho entre o nível atual e o próximo, de 0 a 1; 1 no
            último.
          type: number
    PointEntry:
      description: Uma linha do extrato.
      type: object
      required:
        - id
        - action
        - points
        - created_at
      properties:
        id:
          type: string
          format: uuid
        action:
          $ref: '#/components/schemas/PointAction'
        points:
          description: Negativo só em ajuste.
          type: integer
        title:
          description: >-
            O nome do que gerou o ponto (a aula, o curso, a live, o post).
            Ausente no ajuste, no post sem título e quando a origem foi apagada.
          type: string
        note:
          description: O motivo do ajuste.
          type: string
        created_at:
          type: string
          format: date-time
    GamificationLevel:
      type: object
      required:
        - name
        - min_points
      properties:
        id:
          description: |
            Estável enquanto o nível existir — as automações filtram por ele. Na
            troca da escada, o nível que vem com `id` é atualizado e o que vem
            sem é criado; o que não vier é apagado.
          type: string
          format: uuid
        name:
          type: string
          minLength: 1
          maxLength: 40
        min_points:
          description: A partir de quantos pontos o aluno está neste nível.
          type: integer
          minimum: 0
    PointAction:
      description: |
        O que gera ponto. Todas são disparadas pela plataforma, uma vez por
        origem: `lesson_completed` (a primeira conclusão da aula),
        `course_completed` (a última aula publicada do curso),
        `quiz_passed` (a primeira aprovação no quiz do bloco),
        `assignment_approved` (a entrega aprovada pela equipe),
        `comment_posted` (comentário publicado numa aula, até cinco por dia),
        `post_published` (post na comunidade, até cinco por dia) e
        `live_attended` (entrar numa live). `adjustment` é o ajuste à mão
        da equipe e só aparece no extrato.
      type: string
      enum:
        - lesson_completed
        - course_completed
        - quiz_passed
        - assignment_approved
        - comment_posted
        - post_published
        - live_attended
        - adjustment
  responses:
    BadRequest:
      description: Requisição malformada
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Unauthorized:
      description: Sessão ausente, expirada ou revogada
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Forbidden:
      description: >
        A sessão é válida, mas não age nesta organização. É a mesma resposta
        para

        uma organização que não existe — distinguir as duas deixaria enumerar

        organizações alheias.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    NotFound:
      description: Recurso não encontrado
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    UnprocessableEntity:
      description: Campos obrigatórios ausentes ou inválidos
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    TooManyRequests:
      description: Teto por IP atingido
      headers:
        Retry-After:
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    InternalError:
      description: >-
        Falha inesperada. O corpo nunca traz o erro real — ele fica no log e no
        rastreamento.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
  securitySchemes:
    userSession:
      type: http
      scheme: bearer
      description: |
        Token OPACO de sessão de quem administra, emitido por
        `/v1/auth/admin/sign-in` e verificado contra a nossa tabela — o mesmo
        desenho do `studentSession`, para a outra identidade.

        É o esquema de quem ADMINISTRA — o dashboard. O aluno do classroom usa o
        `studentSession`; uma rota consumida pelos dois declara os dois
        esquemas, e o middleware aceita qualquer um deles. As duas credenciais
        são opacas e chegam pelo mesmo cabeçalho: quem as separa é a tabela em
        que cada uma existe.

        O dashboard o guarda em cookie httpOnly, que o BFF troca pelo
        `Authorization` a cada chamada.

````