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

# API de Missões

> Leia o progresso das missões da sua equipe no SalesOS a partir do seu próprio backend — objetivos do dia, contadores de conclusão e a próxima missão a destacar, feita para telas iniciais de super app e painéis de parceiros.

# API de Missões

<Note>
  Os exemplos abaixo mostram o cabeçalho no formato de rede `Authorization: P2S-SIGN-V1 API_KEY:TIMESTAMP:SIGNATURE`. Para calcular a assinatura no seu código, use o helper `signedRequest` em [Autenticação](/pt/api/authentication).
</Note>

A API de Missões permite que o seu backend leia o **estado das missões de gamificação** dos seus vendedores no SalesOS — quais objetivos estão abertos hoje, quanto já avançou cada um, o que já foi concluído e qual missão destacar em seguida. Ela foi feita para **compor as suas próprias telas** (por exemplo, um bloco na home mostrando "3 de 6 missões concluídas") antes mesmo de o usuário abrir o módulo do SalesOS.

É uma API **somente leitura, servidor a servidor**: você consulta colaboradores por CPF ou e-mail, em lote, e o SalesOS responde com as missões cuja janela de período contém o dia de hoje, calculado no fuso horário da sua empresa.

## Como funciona

1. **Você recebe uma API Key** com o escopo `missions:read` (Admin > Integrações > API Keys)
2. **Seu backend consulta** o estado das missões de um ou vários colaboradores (por CPF ou e-mail)
3. **O SalesOS responde** com as missões de hoje por colaborador — contadores de progresso, a lista completa e a próxima missão ativa

<Note>
  As missões **avançam** pela atividade dentro do SalesOS: o motor escuta eventos (uma visita agendada, uma venda fechada) e incrementa a missão correspondente. Esta API é para **ler** esse estado — combine com um deep link para o módulo do SalesOS na hora da ação.
</Note>

<Warning>
  Este endpoint nunca cria missões. Se as missões do dia de um colaborador ainda não foram provisionadas, a resposta é uma lista vazia com `progress.total = 0` — não é erro, e não é uma escrita silenciosa.
</Warning>

***

## Autenticação

Todas as requisições exigem uma API Key no cabeçalho `Authorization`:

```
Authorization: P2S-SIGN-V1 API_KEY:TIMESTAMP:SIGNATURE
```

Veja a [página de Autenticação](/pt/api/authentication) para detalhes sobre criar e gerenciar API Keys.

| Propriedade          | Detalhes                                                 |
| -------------------- | -------------------------------------------------------- |
| **Cabeçalho**        | `Authorization: P2S-SIGN-V1 API_KEY:TIMESTAMP:SIGNATURE` |
| **Escopo exigido**   | `missions:read`                                          |
| **Limite de uso**    | Configurável por chave (padrão: 1000 requisições/hora)   |
| **Formato da chave** | `sk_live_` (produção) ou `sk_test_` (testes)             |

***

## Ambientes

<Tabs>
  <Tab title="Produção">
    **URL base:** `https://api.play2sell.com`
  </Tab>

  <Tab title="Staging">
    **URL base:** `https://api-staging.play2sell.com`
  </Tab>
</Tabs>

***

## Referência do endpoint

```
POST https://api.play2sell.com/functions/v1/missions-partner-api
```

O endpoint aceita duas ações no campo `action`: `missions_status` e `missions_summary`.

Cada colaborador é identificado por **CPF** (em qualquer formato — os dígitos são normalizados) ou **e-mail**. Quando os dois são enviados, o CPF prevalece.

***

### Ação: missions\_status

As missões cuja janela de período contém o dia de hoje, por colaborador. "Hoje" é calculado no fuso horário da sua empresa (devolvido em `timezone` / `reference_date` na resposta) — nunca em UTC.

#### Esquema da requisição

<ParamField body="action" type="string" required>
  Deve ser `"missions_status"`
</ParamField>

<ParamField body="collaborators" type="array" required>
  Lista de referências de colaboradores (máximo 500 — 100 quando `include_missions` está ligado)
</ParamField>

<ParamField body="include_missions" type="boolean" default="false">
  Devolve a lista completa `missions[]`. Desligado por padrão: cada colaborador tem várias missões, então um lote grande com a lista ligada vira uma resposta de megabytes. `progress` e `next_mission` vêm sempre.
</ParamField>

<ParamField body="collaborators[].cpf" type="string">
  CPF, em qualquer formato (`52998224725` ou `529.982.247-25`). Deve conter exatamente 11 dígitos.
</ParamField>

<ParamField body="collaborators[].email" type="string">
  E-mail — usado apenas quando o `cpf` está ausente
</ParamField>

#### Exemplo

```bash theme={null}
curl -X POST https://api.play2sell.com/functions/v1/missions-partner-api \
  -H "Authorization: P2S-SIGN-V1 API_KEY:TIMESTAMP:SIGNATURE" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "missions_status",
    "collaborators": [
      { "cpf": "529.982.247-25" },
      { "email": "joao@suaempresa.com" }
    ]
  }'
```

**Resposta (200):**

```json theme={null}
{
  "data": {
    "timezone": "America/Sao_Paulo",
    "reference_date": "2026-08-11",
    "collaborators": [
      {
        "cpf": "52998224725",
        "found": true,
        "user_id": "8f14e45f-0000-0000-0000-000000000001",
        "name": "Maria Santos",
        "membership_status": "active",
        "progress": {
          "total": 6,
          "completed": 3,
          "pending_approval": 1,
          "active": 2,
          "expired": 0,
          "points_earned": 90,
          "points_available": 235,
          "points_pending_approval": 0
        },
        "next_mission": {
          "id": "b1a2c3d4-0000-0000-0000-000000000010",
          "key": "schedule_visit",
          "name": "Agendar 1 visita",
          "description": "Agende uma visita com um lead",
          "category": "sales",
          "icon": "calendar",
          "current_count": 0,
          "target_count": 1,
          "points_reward": 35,
          "nominal_reward": 70,
          "action": { "route": "/leads", "label": "Ver leads", "params": null }
        },
        "missions": [
          {
            "id": "b1a2c3d4-0000-0000-0000-000000000009",
            "key": "call_3_leads",
            "name": "Ligar para 3 leads",
            "category": "sales",
            "period_type": "daily",
            "period_start": "2026-08-11",
            "period_end": "2026-08-11",
            "icon": "phone",
            "status": "completed",
            "current_count": 3,
            "target_count": 3,
            "points_reward": 30,
            "nominal_reward": 30,
            "points_credited_so_far": 30,
            "points_awarded": 30,
            "completed_at": "2026-08-11T13:20:04Z",
            "action": null
          }
        ]
      },
      { "email": "joao@suaempresa.com", "found": false }
    ],
    "total": 2,
    "found": 1
  },
  "meta": {
    "request_id": "1f6c1b2e-0000-4a1b-8c2d-000000000001",
    "timestamp": "2026-08-11T13:45:00.000Z"
  }
}
```

#### Campos da resposta

| Campo                               | Significado                                                                                                                                                                                            |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `timezone` / `reference_date`       | O fuso da sua empresa e o dia local a que a resposta se refere                                                                                                                                         |
| `found`                             | Se o CPF/e-mail resolveu para um colaborador da sua empresa                                                                                                                                            |
| `membership_status`                 | Situação do vínculo do colaborador com a sua empresa                                                                                                                                                   |
| `progress.total`                    | Missões cuja janela de período contém hoje                                                                                                                                                             |
| `progress.completed`                | Concluídas **e** recompensadas                                                                                                                                                                         |
| `progress.pending_approval`         | Concluídas pelo colaborador, aguardando o gestor — pontos **ainda não** creditados                                                                                                                     |
| `progress.active`                   | Ainda em andamento                                                                                                                                                                                     |
| `progress.expired`                  | Período encerrado sem conclusão                                                                                                                                                                        |
| `progress.points_earned`            | Pontos **efetivamente creditados** até agora: a recompensa cheia das missões concluídas mais as parcelas já pagas nas que estão em andamento                                                           |
| `progress.points_available`         | O que **ainda dá para conquistar** nas missões ativas — a recompensa efetiva menos as parcelas já creditadas. Missões de penalidade (recompensa negativa) ficam de fora: penalidade é risco, não ganho |
| `progress.points_pending_approval`  | Recompensas travadas aguardando aprovação do gestor. Nada foi creditado ainda                                                                                                                          |
| `next_mission`                      | A missão ativa de menor ordem de exibição — a que deve ser destacada. Ausente quando não há nenhuma ativa                                                                                              |
| `missions[]`                        | A lista completa, na ordem de exibição configurada pela empresa                                                                                                                                        |
| `missions[].points_reward`          | A recompensa **efetiva** — o que este colaborador vai receber de fato, escalada pelo alvo dele (veja abaixo)                                                                                           |
| `missions[].nominal_reward`         | A recompensa configurada na definição da missão, antes da escala. Mesmos nomes de campo que o evento `mission.completed` usa                                                                           |
| `missions[].points_credited_so_far` | Quanto da recompensa desta missão já foi pago pelo progresso                                                                                                                                           |
| `missions[].action`                 | Deep link opcional configurado para a missão (`route`, `label`, `params`). `null` quando não houver                                                                                                    |

<Warning>
  **A lista completa é opt-in.** Por padrão a resposta traz apenas `progress` e `next_mission` — o suficiente para desenhar uma tela inicial. Peça `include_missions: true` quando precisar de todas as missões, e conte com um limite de lote menor (100 em vez de 500), porque cada colaborador carrega várias missões.
</Warning>

<Warning>
  **As recompensas são personalizadas — exiba sempre `points_reward`, nunca `nominal_reward`.** O alvo de uma missão pode ser adaptado por colaborador, e a recompensa acompanha: quem teve o alvo reduzido pela metade ganha metade dos pontos. `points_reward` é o que será realmente creditado; `nominal_reward` é o número configurado na definição, enviado só para você poder indicar "meta reduzida" se quiser. São os mesmos nomes de campo que o webhook `mission.completed` usa, então os dois canais concordam.
</Warning>

<Note>
  **A recompensa é paga em parcelas, então "conquistado" e "ainda a conquistar" são números diferentes.** Uma missão de 100 pontos com meta 5 credita 20 a cada passo. Depois de um passo, o colaborador **conquistou 20** e ainda tem **80 a conquistar** — `points_earned` conta os 20, `points_available` conta os 80. Somar os 100 cheios nos dois lados contaria os mesmos pontos duas vezes.
</Note>

<Tip>
  **`pending_approval` não é `completed`.** Algumas missões exigem aprovação do gestor antes de creditar os pontos, e o crédito acontece na data da aprovação. Reportar em separado permite exibir "aguardando aprovação" sem inventar a distinção.
</Tip>

<Warning>
  Nomes de missão, categorias, ícones, recompensas, metas e ordem de exibição são **configurados por empresa**. Nunca deixe esses valores fixos no seu cliente — renderize o que vier na resposta, para que uma mudança no SalesOS não exija uma nova versão do app.
</Warning>

***

### Ação: missions\_summary

Tudo o que `missions_status` devolve, mais os contadores de conclusão da semana e do mês. Útil para uma faixa de "seu mês até aqui".

As missões são contadas no período a que **pertencem** (a janela delas), não na data em que foram aprovadas — então uma missão aprovada com atraso continua contando na semana em que foi conquistada.

#### Exemplo

```bash theme={null}
curl -X POST https://api.play2sell.com/functions/v1/missions-partner-api \
  -H "Authorization: P2S-SIGN-V1 API_KEY:TIMESTAMP:SIGNATURE" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "missions_summary",
    "collaborators": [{ "cpf": "529.982.247-25" }]
  }'
```

**Resposta (200) — bloco adicional por colaborador:**

```json theme={null}
{
  "summary": {
    "week":  { "completed": 8,  "points": 240 },
    "month": { "completed": 31, "points": 980 }
  }
}
```

<Note>
  `missions_summary` varre um mês de registros por colaborador, por isso o limite de lote é **100** em vez de 500.
</Note>

***

## Modo self (sessão federada)

Quando o seu app já tem um token de usuário federado do SalesOS, envie-o como `Authorization: Bearer <jwt>` e **omita** `collaborators`. A resposta cobre apenas o próprio usuário do token.

```bash theme={null}
curl -X POST https://api.play2sell.com/functions/v1/missions-partner-api \
  -H "Authorization: Bearer <jwt-federado>" \
  -H "Content-Type: application/json" \
  -d '{ "action": "missions_status" }'
```

<Warning>
  Enviar `collaborators` junto com um token de usuário é rejeitado com `SELF_MODE_NO_COLLABORATORS`. Consultas em lote exigem uma API Key de parceiro — do contrário, qualquer usuário logado poderia ler o progresso de outras pessoas por CPF.
</Warning>

***

## Tratamento de erros

Todos os erros seguem a mesma estrutura:

```json theme={null}
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Descrição legível",
    "details": []
  }
}
```

<Warning>
  **São dois formatos, não um.** O bloco acima é o erro do *endpoint*. Falhas na **camada de autenticação** respondem antes de o endpoint rodar, com `error` como **string**:

  ```json theme={null}
  { "error": "Missing Authorization header", "code": "header_missing" }
  ```

  Código que assume `error.code` lê `undefined` em toda falha de autenticação. Ramifique pelo tipo — veja [Convenções da API](/pt/api/conventions#formatos-de-erro).
</Warning>

<AccordionGroup>
  <Accordion title="400 — VALIDATION_ERROR">
    Corpo inválido, lote acima do limite, CPF sem 11 dígitos, ou item sem `cpf` e sem `email`. A lista `details` aponta o `index` do item.
  </Accordion>

  <Accordion title="400 — SELF_MODE_NO_COLLABORATORS">
    Uma lista `collaborators` foi enviada junto com um token de usuário. Omita-a, ou use uma API Key de parceiro.
  </Accordion>

  <Accordion title="401 — UNAUTHORIZED">
    API Key ausente, inválida ou expirada, ou assinatura que não confere.
  </Accordion>

  <Accordion title="403 — FORBIDDEN">
    API Key válida, mas sem o escopo `missions:read`.
  </Accordion>

  <Accordion title="405 — METHOD_NOT_ALLOWED">
    Apenas `POST` é aceito.
  </Accordion>

  <Accordion title="429 — RATE_LIMITED">
    Requisições demais nesta hora. Aguarde `retry_after` segundos e tente de novo.
  </Accordion>

  <Accordion title="500 — SERVER_ERROR">
    Erro interno. Tente novamente com espera progressiva (2s, 4s, 8s).
  </Accordion>
</AccordionGroup>

<Tip>
  **Um colaborador desconhecido não é erro.** A requisição é bem-sucedida com `found: false` para aquele item, então um CPF errado nunca derruba a renderização da tela inteira.
</Tip>

***

## Limites de uso

| Limite                                         | Valor                            |
| ---------------------------------------------- | -------------------------------- |
| Requisições por hora (padrão)                  | 1000                             |
| Máximo de colaboradores por `missions_status`  | 500 (100 com `include_missions`) |
| Máximo de colaboradores por `missions_summary` | 100 (50 com `include_missions`)  |

***

## Segurança

* Somente leitura: esta API nunca cria, avança ou provisiona missões
* Cada chave pertence a uma única empresa — um CPF de outra empresa responde `found: false`
* As requisições são assinadas por HMAC (P2S-SIGN-V1) e registradas para auditoria; documentos nunca são gravados nos logs

<Warning>
  Nunca exponha sua API Key em código do lado do cliente. Esta API deve ser chamada apenas pelo seu servidor.
</Warning>

***

## Próximos passos

<CardGroup cols={2}>
  <Card title="API de Check-in" icon="location-dot" href="/pt/api/integrations/checkin">
    Leia a presença em serviço para completar a mesma tela inicial
  </Card>

  <Card title="Autenticação" icon="key" href="/pt/api/authentication">
    Aprenda a criar e gerenciar API Keys
  </Card>
</CardGroup>
