> ## 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.

# Player Progress API

> Read level, XP, coins and ranking position for your collaborators — built for the identity band on a super-app home screen.

# Player Progress API

<Note>
  This page assumes the shared rules in [API Conventions](/api/conventions) — authentication, the two error shapes, batching and identity resolution. It only covers what is specific to progress.
</Note>

The Player Progress API lets your backend read where each collaborator stands in your company's gamification: **which level they are on**, **how much XP they have**, **how many coins they can spend**, and **their position in the ranking**.

It was built for the band at the top of a home screen — the one that greets the person and shows how they are doing.

<Note>
  The **level ladder is configured by your company**: how many levels, their names, what each one requires, and how each one looks. This API returns whatever is configured — it knows no specific ladder.
</Note>

## How It Works

1. **You get an API Key** with the `progress:read` scope
2. **Your backend asks** for one or many collaborators (by CPF or email)
3. **SalesOS answers** with the ladder **once at the top**, then each person's state

***

## Authentication

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

| Property           | Details                                            |
| ------------------ | -------------------------------------------------- |
| **Scope required** | `progress:read`                                    |
| **Method**         | `POST` only                                        |
| **Rate limit**     | Configurable per key (default: 1000 requests/hour) |

***

## Endpoint Reference

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

One action: `progress_status`.

### Request Schema

<ParamField body="action" type="string" required>
  Must be `"progress_status"`
</ParamField>

<ParamField body="collaborators" type="array" required>
  Array of collaborator references (max 500)
</ParamField>

<ParamField body="period" type="string">
  Period for XP and ranking: `daily`, `weekly`, `monthly` or `all_time`. Absent = `all_time`.
</ParamField>

<ParamField body="ranking_scope" type="string">
  Ranking universe: `tenant` (everyone in the company) or `org_unit` (the person's own unit). Absent = `tenant`.
</ParamField>

### Example

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

**Response (200):**

```json theme={null}
{
  "data": {
    "timezone": "America/Sao_Paulo",
    "reference_date": "2026-08-14",
    "period": "all_time",
    "ranking_scope": "tenant",
    "levels": [
      { "order": 1, "key": "level_01", "name": "Starter",
        "requirements": [], "appearance": null },
      { "order": 2, "key": "level_02", "name": "Advanced",
        "requirements": [{ "type": "min_xp", "value": 500 }],
        "appearance": { "color": "#7B2D8E" } }
    ],
    "collaborators": [
      {
        "cpf": "52998224725",
        "found": true,
        "user_id": "8f14e45f-0000-0000-0000-000000000001",
        "name": "Maria Santos",
        "membership_status": "active",
        "level": {
          "order": 2, "key": "level_02", "name": "Advanced",
          "appearance": { "color": "#7B2D8E" }
        },
        "next_level": {
          "order": 3, "key": "level_03", "name": "Legend",
          "requirements": [{ "type": "min_xp", "value": 2000 }],
          "appearance": null
        },
        "xp": {
          "period": "all_time", "current": 1200,
          "all_time": 1200, "monthly": 300, "daily": 0
        },
        "coins": { "balance": 640, "source": "playcoin" },
        "ranking": { "scope": "tenant", "period": "all_time", "position": 1, "total": 240 }
      }
    ],
    "total": 1,
    "found": 1
  },
  "meta": {
    "request_id": "1f6c1b2e-0000-4a1b-8c2d-000000000001",
    "timestamp": "2026-08-14T13:45:00.000Z"
  }
}
```

***

## Fields

### The ladder (top level)

| Field                   | Meaning                                                       |
| ----------------------- | ------------------------------------------------------------- |
| `levels[]`              | Your company's level ladder, ordered from first to last       |
| `levels[].order`        | Position on the ladder — this is what `level.order` refers to |
| `levels[].name`         | The level name **as your company configured it**              |
| `levels[].requirements` | What that level demands (e.g. `min_xp`, `min_missions_count`) |
| `levels[].appearance`   | Visual configuration for the level — see the warning below    |

<Warning>
  **The number of levels is your company's, not a constant.** Build the screen from the array. A ladder can have nine rungs where another has ten, and both are correct.
</Warning>

### Per collaborator

| Field                                          | Meaning                                                                              |
| ---------------------------------------------- | ------------------------------------------------------------------------------------ |
| `level`                                        | The level the person is on right now, with its name and appearance                   |
| `next_level`                                   | The next rung and what it requires. **Absent** when the person is at the top         |
| `xp.period`                                    | The period you asked for, echoed back                                                |
| `xp.current`                                   | XP in that period — the number to show next to the name                              |
| `xp.all_time` · `monthly` · `weekly` · `daily` | The same score in every period. **A period with no measurement is absent, not zero** |
| `coins.balance`                                | Coins available to spend                                                             |
| `ranking.scope`                                | The universe the position was computed in                                            |
| `ranking.position` · `total`                   | Position and how many people are in that universe                                    |

***

## Level appearance

The level's visual identity — its color, and anything else your company configures — comes back in `appearance`.

<Warning>
  **`appearance` can be `null`, and null does not mean "no color".** It means nobody has configured one yet. Render your own fallback in that case, but do not hardcode a palette keyed by level name: the day your company renames a level or changes its colors, a hardcoded screen keeps showing the old one, with no error anywhere.
</Warning>

<Tip>
  Read the color from the response even when it looks constant today. That is the whole reason it travels in the payload instead of living in your app.
</Tip>

***

## Periods

XP is stored in four periods, and the screen usually shows one of them. Because "200 XP" on a design does not say which, this API returns **all four** plus the one you asked for.

| `period`   | Covers                             |
| ---------- | ---------------------------------- |
| `daily`    | Today, in the company's timezone   |
| `weekly`   | The current week                   |
| `monthly`  | The current month                  |
| `all_time` | Everything since the person joined |

<Warning>
  An unknown period is **rejected with 400**, not answered with zeros. A response full of zeros would make your screen state that the person has no XP — an absence that is not true.
</Warning>

<Warning>
  **A period that was never measured comes back absent, not as `0`.** In the example above `weekly` is missing: that person has no weekly score row. `daily: 0` is different — it was measured, and the result was zero.

  Treat the two apart. Writing "0 XP this week" for an absent period tells the person they scored nothing, when the truth is nobody computed that period for them.
</Warning>

***

## Ranking

A position only means something alongside its universe, so `scope`, `period` and `total` always travel with it.

| `ranking_scope` | Universe                   |
| --------------- | -------------------------- |
| `tenant`        | Everyone in the company    |
| `org_unit`      | Only the person's own unit |

<Tip>
  `total` is how many people the position was measured against. "#12 of 240" reads very differently from "#12 of 13" — show it.
</Tip>

***

## Error Handling

See [API Conventions](/api/conventions#error-shapes) for the two shapes and the status code table.

<AccordionGroup>
  <Accordion title="400 — VALIDATION_ERROR">
    Invalid body, batch over 500, CPF without 11 digits, an item with neither `cpf` nor `email`, or an unknown `period` / `ranking_scope`.
  </Accordion>

  <Accordion title="403 — FORBIDDEN">
    API key is valid but lacks the `progress:read` scope.
  </Accordion>
</AccordionGroup>

<Tip>
  **A company with no ladder configured is not an error.** The response comes back with `levels: []` and status 200.
</Tip>

***

## Rate Limits

| Limit                         | Value |
| ----------------------------- | ----- |
| Default requests per hour     | 1000  |
| Max collaborators per request | 500   |

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Missions API" icon="bullseye-arrow" href="/api/integrations/missions">
    The progress that earns the XP shown here
  </Card>

  <Card title="Campaigns API" icon="gift" href="/api/integrations/campaigns">
    Where the coins get spent
  </Card>
</CardGroup>
