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

# Check-in API

> Read your team's SalesOS check-in (duty presence) state from your own backend — built for super-app home screens and partner dashboards.

# Check-in API

<Note>
  Examples below show the wire-format `Authorization: P2S-SIGN-V1 API_KEY:TIMESTAMP:SIGNATURE` header. To compute the signature in your code, use the `signedRequest` helper in [Authentication](/api/authentication).
</Note>

The Check-in API lets your backend read the **duty presence state** of your salespeople in SalesOS — did they check in today, where, on which shift, and until when it is valid. It was built for **composing your own app screens** (for example, a super-app home widget) before the user ever opens the SalesOS module.

It is a **read-only, server-to-server** API: you look collaborators up by CPF or email, in batch, and SalesOS answers with today's state computed in your company's timezone.

## How It Works

1. **You get an API Key** with the `checkin:read` scope (Admin > Integrations > API Keys)
2. **Your backend asks** for the check-in state of one or many collaborators (by CPF or email)
3. **SalesOS answers** with today's latest check-in per collaborator — plus optional week/month engagement counts

<Note>
  Check-ins are still **performed** inside the SalesOS app (GPS validation happens in the user's context). This API is for **reading** presence state — pair it with a deep link into the SalesOS module for the "Check in now" action.
</Note>

***

## Authentication

All requests require an API Key in the `Authorization` header:

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

See the [Authentication page](/api/authentication) for details on creating and managing API Keys.

| Property           | Details                                                  |
| ------------------ | -------------------------------------------------------- |
| **Header**         | `Authorization: P2S-SIGN-V1 API_KEY:TIMESTAMP:SIGNATURE` |
| **Scope required** | `checkin:read`                                           |
| **Rate limit**     | Configurable per key (default: 1000 requests/hour)       |
| **Key format**     | `sk_live_` (production) or `sk_test_` (testing)          |

***

## Environments

<Tabs>
  <Tab title="Production">
    **Base URL:** `https://api.play2sell.com`
  </Tab>

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

***

## Endpoint Reference

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

The endpoint accepts two actions via the `action` field: `checkin_status` and `checkin_engagement`.

Each collaborator is identified by **CPF** (any format — digits are normalized) or **email**. When both are sent, CPF wins.

***

### Action: checkin\_status

Today's latest check-in per collaborator. "Today" is computed in your company's timezone (returned as `timezone` / `reference_date` in the response) — never UTC.

#### Request Schema

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

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

<ParamField body="collaborators[].cpf" type="string">
  CPF, any format (`52998224725` or `529.982.247-25`). Must contain exactly 11 digits.
</ParamField>

<ParamField body="collaborators[].email" type="string">
  Email — used only when `cpf` is absent
</ParamField>

#### Example

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

**Response (200):**

```json theme={null}
{
  "data": {
    "timezone": "America/Sao_Paulo",
    "reference_date": "2026-07-19",
    "collaborators": [
      {
        "cpf": "52998224725",
        "found": true,
        "user_id": "8f14e45f-...",
        "name": "Maria Santos",
        "membership_status": "active",
        "checked_in_today": true,
        "on_duty_now": true,
        "latest_checkin": {
          "status": "active",
          "mode": "geo",
          "shift": "afternoon",
          "checked_in_at": "2026-07-19T16:03:21+00:00",
          "shift_expires_at": "2026-07-19T21:00:00+00:00",
          "location_id": "c81e728d-...",
          "location_name": "Plantão Morumbi"
        },
        "org_unit_id": "a87ff679-..."
      },
      {
        "email": "joao@yourcompany.com",
        "found": true,
        "user_id": "45c48cce-...",
        "name": "Joao Silva",
        "membership_status": "active",
        "checked_in_today": false,
        "on_duty_now": false
      }
    ],
    "total": 2,
    "found": 2
  },
  "meta": { "request_id": "a1b2c3d4-...", "timestamp": "2026-07-19T18:00:00.000Z" }
}
```

#### Response fields explained

<ResponseField name="found" type="boolean">
  Whether the CPF/email matched a collaborator **in your company**. An unknown collaborator is `found: false` — it is not an error, and the array keeps the same order as your request.
</ResponseField>

<ResponseField name="checked_in_today" type="boolean">
  Whether the collaborator has at least one non-rejected check-in today (company timezone).
</ResponseField>

<ResponseField name="on_duty_now" type="boolean">
  `true` when the latest check-in is active/approved and its shift has not expired — i.e. the collaborator is on duty right now.
</ResponseField>

<ResponseField name="latest_checkin" type="object">
  Today's most recent check-in. Omitted when there is none. `mode` is `geo` (in-person with GPS), `home` (home office) or `offline`. `status` may be `active`, `approved`, `pending` (awaiting leader approval), `expired` (shift ended normally) or `checkout`.
</ResponseField>

<ResponseField name="membership_status" type="string">
  The collaborator's membership in your company (`active`, `inactive`, ...). A collaborator can be found and inactive.
</ResponseField>

***

### Action: checkin\_engagement

Everything `checkin_status` returns, plus week/month engagement counts per collaborator. Batches are capped at 100.

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

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

#### Example

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

Each found collaborator gains an `engagement` object:

```json theme={null}
{
  "engagement": {
    "week":  { "checkins": 9,  "presence_days": 5 },
    "month": { "checkins": 34, "presence_days": 17 }
  }
}
```

* `checkins` — check-ins in the current week/month (company timezone; `expired` counts — it is the normal end of a shift)
* `presence_days` — distinct days with at least one check-in

***

## Self mode (federated session)

If your app already holds a **federated SalesOS session** — the JWT returned by a custom login such as the Superapp federation — the same endpoint answers for the token's own user, with no API key:

```bash theme={null}
curl -X POST https://api.play2sell.com/functions/v1/checkin-partner-api \
  -H "Authorization: Bearer SESSION_JWT" \
  -H "Content-Type: application/json" \
  -d '{ "action": "checkin_status" }'
```

* Identity comes **only** from the verified token — do **not** send `collaborators`. A request with that field is rejected with `SELF_MODE_NO_COLLABORATORS`: user-level tokens can never look other people up.
* The company scope comes from the session; the response has the exact same shape, with a single item in `collaborators`.
* `checkin_engagement` works the same way (`{ "action": "checkin_engagement" }`).

<Tip>
  Use self mode for screens rendered **inside** the authenticated app; use the API key (S2S) when your backend composes screens **before** the user has a SalesOS session — like a super-app home widget.
</Tip>

***

## Error Handling

All errors follow the shared structure:

```json theme={null}
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description",
    "details": []
  }
}
```

<AccordionGroup>
  <Accordion title="400 — VALIDATION_ERROR">
    Invalid body, batch over the limit, CPF without 11 digits, or an item with neither `cpf` nor `email`. The `details` array points to the item `index`.
  </Accordion>

  <Accordion title="401 — UNAUTHORIZED">
    API key is missing, invalid, or expired, or the signature does not match.
  </Accordion>

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

  <Accordion title="405 — METHOD_NOT_ALLOWED">
    Only `POST` is accepted.
  </Accordion>

  <Accordion title="429 — RATE_LIMITED">
    Too many requests this hour. Wait `retry_after` seconds, then retry.
  </Accordion>

  <Accordion title="500 — SERVER_ERROR">
    Internal server error. Retry with exponential backoff (2s, 4s, 8s).
  </Accordion>
</AccordionGroup>

<Tip>
  **An unknown collaborator is not an error.** The request succeeds with `found: false` for that item, so one bad CPF never breaks a whole home-screen render.
</Tip>

***

## Rate Limits

| Limit                                      | Value |
| ------------------------------------------ | ----- |
| Default requests per hour                  | 1000  |
| Max collaborators per `checkin_status`     | 500   |
| Max collaborators per `checkin_engagement` | 100   |

***

## Security

* Read-only: this API never creates or changes check-ins
* Each key is scoped to a single company — a CPF from another company answers `found: false`
* Requests are HMAC-signed (P2S-SIGN-V1) and logged for audit; documents are never written to logs

<Warning>
  Never expose your API key in client-side code. This API must only be called from your backend server.
</Warning>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/api/authentication">
    Learn how to create and manage API Keys
  </Card>

  <Card title="Support" icon="headset" href="mailto:suporte@play2sell.com">
    Need help? Contact our support team
  </Card>
</CardGroup>
