> ## 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 Campañas

> Lea las campañas de incentivo de su empresa desde su propio backend — catálogo de premios, saldo del colaborador y lo que ya alcanza, diseñada para pantallas de inicio de súper apps y paneles de socios.

# API de Campañas

<Note>
  Los ejemplos siguientes muestran el encabezado en formato de red `Authorization: P2S-SIGN-V1 API_KEY:TIMESTAMP:SIGNATURE`. Para calcular la firma en su código, use el helper `signedRequest` en [Autenticación](/es/api/authentication).
</Note>

La API de Campañas permite que su backend lea las **campañas de incentivo** de su empresa en SalesOS — cuáles están vigentes, qué ofrece el catálogo, cuánto tiene cada colaborador para gastar y cuántos premios ya alcanza. Fue diseñada para **componer sus propias pantallas** antes de que el usuario abra el módulo de SalesOS.

Es una API **de solo lectura, servidor a servidor**: usted consulta colaboradores por CPF o correo, en lote, y SalesOS responde con las campañas vigentes y el estado de cada persona en ellas.

<Note>
  Una campaña la **configura su empresa**: nombre, catálogo, precios en puntos, categorías, reglas de nivel e incluso el nombre de la moneda. Esta API devuelve lo que esté configurado — no conoce ninguna campaña específica.
</Note>

## Cómo funciona

1. **Usted recibe una API Key** con el alcance `campaigns:read` (Admin > Integraciones > API Keys)
2. **Su backend consulta** el estado de uno o varios colaboradores (por CPF o correo)
3. **SalesOS responde** con las campañas vigentes — los datos de la campaña **una vez**, y luego el estado de cada colaborador

<Note>
  Los metadatos de la campaña salen **una sola vez arriba**, no repetidos por persona. En un lote de 500 colaboradores esa es la diferencia entre una respuesta liviana y una de megabytes.
</Note>

<Warning>
  Este endpoint **no** canjea premios ni acepta términos. Es de lectura — combínelo con un enlace profundo al módulo de SalesOS para la acción en sí.
</Warning>

***

## Autenticación

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

Consulte la [página de Autenticación](/es/api/authentication) para crear y gestionar API Keys.

| Propiedad             | Detalles                                                       |
| --------------------- | -------------------------------------------------------------- |
| **Encabezado**        | `Authorization: P2S-SIGN-V1 API_KEY:TIMESTAMP:SIGNATURE`       |
| **Alcance requerido** | `campaigns:read`                                               |
| **Límite de uso**     | Configurable por clave (predeterminado: 1000 solicitudes/hora) |
| **Formato de clave**  | `sk_live_` (producción) o `sk_test_` (pruebas)                 |

***

## Entornos

<Tabs>
  <Tab title="Producción">
    **URL base:** `https://api.play2sell.com`
  </Tab>

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

***

## Referencia del endpoint

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

Dos acciones en el campo `action`: `campaign_status` y `campaign_catalog`.

Cada colaborador se identifica por **CPF** (en cualquier formato — los dígitos se normalizan) o **correo**. Cuando se envían ambos, prevalece el CPF.

***

### Acción: campaign\_status

Las campañas vigentes de su empresa y el estado de cada colaborador en ellas.

#### Esquema de la solicitud

<ParamField body="action" type="string" required>
  Debe ser `"campaign_status"`
</ParamField>

<ParamField body="collaborators" type="array" required>
  Lista de referencias de colaboradores (máximo 500)
</ParamField>

<ParamField body="collaborators[].cpf" type="string">
  CPF, en cualquier formato (`52998224725` o `529.982.247-25`). Debe contener exactamente 11 dígitos.
</ParamField>

<ParamField body="collaborators[].email" type="string">
  Correo — usado solo cuando `cpf` está ausente
</ParamField>

<ParamField body="campaign_slug" type="string">
  Filtra una campaña específica. Ausente = todas las vigentes.
</ParamField>

<ParamField body="locale" type="string">
  Idioma de los textos configurables. Ausente = el predeterminado de la campaña.
</ParamField>

#### Ejemplo

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

**Respuesta (200):**

```json theme={null}
{
  "data": {
    "timezone": "America/Sao_Paulo",
    "reference_date": "2026-08-12",
    "campaigns": [
      {
        "id": "aaaaaaaa-0000-0000-0000-000000000001",
        "slug": "su-campana",
        "name": "Su Campaña",
        "tagline": "Su catálogo de premios",
        "status": "active",
        "default_locale": "pt",
        "locales": ["pt"],
        "currency": { "label": "pontos" },
        "catalog": {
          "rewards": 22,
          "awarded_only": 2,
          "categories": 6,
          "cheapest_points": 200,
          "priciest_points": 22000
        }
      }
    ],
    "collaborators": [
      {
        "cpf": "52998224725",
        "found": true,
        "user_id": "8f14e45f-0000-0000-0000-000000000001",
        "name": "Maria Santos",
        "membership_status": "active",
        "balance": {
          "spendable": 1500,
          "source": "playcoin",
          "wallet_enabled": true
        },
        "score": { "xp": 1800, "level": 3, "note": "diverges_from_balance" },
        "campaigns": [
          {
            "campaign_id": "aaaaaaaa-0000-0000-0000-000000000001",
            "slug": "su-campana",
            "eligible_rewards": 18,
            "affordable_rewards": 7,
            "next_reachable_points": 2000,
            "redemptions": { "open": 1, "fulfilled": 3 }
          }
        ]
      }
    ],
    "total": 1,
    "found": 1
  },
  "meta": {
    "request_id": "1f6c1b2e-0000-4a1b-8c2d-000000000001",
    "timestamp": "2026-08-12T13:45:00.000Z"
  }
}
```

#### Campos de la campaña

| Campo                                         | Significado                                                                                                         |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `slug` · `name` · `tagline`                   | Identificación y textos configurados por su empresa                                                                 |
| `currency.label`                              | **El nombre de la moneda, tal como su empresa lo configuró.** Renderice este valor — nunca fije un nombre en su app |
| `catalog.rewards`                             | Premios que pueden **canjearse con saldo**                                                                          |
| `catalog.awarded_only`                        | Premios **otorgados** (por ranking o disparador), que no están a la venta                                           |
| `catalog.categories`                          | Categorías distintas en el catálogo                                                                                 |
| `catalog.cheapest_points` / `priciest_points` | Rango de precios del catálogo                                                                                       |
| `terms`                                       | Presente solo cuando la campaña exige aceptación, con la versión vigente                                            |

#### Campos del colaborador

| Campo                               | Significado                                                                          |
| ----------------------------------- | ------------------------------------------------------------------------------------ |
| `balance.spendable`                 | **El saldo disponible para gastar ahora**                                            |
| `balance.wallet_enabled`            | Si la billetera está habilitada para su empresa — **léalo antes de mostrar un cero** |
| `score.xp` · `score.level`          | Puntuación y nivel. El nivel determina qué premios alcanza el colaborador            |
| `score.note`                        | `diverges_from_balance` cuando puntuación y saldo no coinciden                       |
| `campaigns[].eligible_rewards`      | Premios que **alcanza** (regla de nivel satisfecha)                                  |
| `campaigns[].affordable_rewards`    | Premios que **puede pagar** con el saldo actual                                      |
| `campaigns[].next_reachable_points` | Cuánto cuesta el próximo premio fuera de alcance — el "faltan X"                     |
| `campaigns[].terms_accepted`        | Presente solo cuando la campaña exige aceptación                                     |
| `campaigns[].redemptions`           | Canjes abiertos y completados                                                        |

<Warning>
  **`spendable: 0` no significa "sin puntos".** La billetera se habilita por empresa; mientras no lo esté, el saldo llega en cero incluso para quien tiene puntuación. Lea siempre `wallet_enabled` antes de escribir "sin saldo" en la pantalla — de lo contrario afirmará una ausencia que no es cierta.
</Warning>

<Tip>
  **Alcanzar y poder pagar son cuentas distintas.** Un premio puede estar liberado por nivel y aun así costar más de lo que el colaborador tiene. Por eso `eligible_rewards` y `affordable_rewards` son campos separados.
</Tip>

***

### Acción: campaign\_catalog

El mismo contenido con el recorte de catálogo. Como el catálogo crece por colaborador, el **límite de lote baja a 100**.

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

***

## Modo self (sesión federada)

Con un token de usuario federado, envíe `Authorization: Bearer <jwt>` y **omita** `collaborators`. La respuesta cubre solo al propio usuario del token.

<Warning>
  Enviar `collaborators` con un token de usuario se rechaza con `SELF_MODE_NO_COLLABORATORS`. Las consultas en lote requieren una API Key de socio — de lo contrario, cualquier usuario autenticado podría leer el saldo de otras personas por CPF.
</Warning>

***

## Manejo de errores

```json theme={null}
{ "error": { "code": "ERROR_CODE", "message": "Descripción legible", "details": [] } }
```

<Warning>
  **Son dos formatos, no uno.** El bloque anterior es el error del *endpoint*. Las fallas en la **capa de autenticación** responden antes de que el endpoint corra, con `error` como **cadena**:

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

  El código que asume `error.code` lee `undefined` en toda falla de autenticación. Ramifique por el tipo — vea [Convenciones de la API](/es/api/conventions#formatos-de-error).
</Warning>

<AccordionGroup>
  <Accordion title="400 — VALIDATION_ERROR">
    Cuerpo inválido, lote por encima del límite, CPF sin 11 dígitos, un elemento sin `cpf` ni `email`, o `campaign_slug` malformado. La lista `details` indica el `index` del elemento.
  </Accordion>

  <Accordion title="400 — SELF_MODE_NO_COLLABORATORS">
    Se envió una lista `collaborators` junto con un token de usuario.
  </Accordion>

  <Accordion title="401 — UNAUTHORIZED">
    API Key ausente, inválida o expirada, o firma que no coincide.
  </Accordion>

  <Accordion title="403 — FORBIDDEN">
    API Key válida, pero sin el alcance `campaigns:read`.
  </Accordion>

  <Accordion title="405 — METHOD_NOT_ALLOWED">
    Solo se acepta `POST`.
  </Accordion>

  <Accordion title="429 — RATE_LIMITED">
    Demasiadas solicitudes en esta hora. Espere `retry_after` segundos.
  </Accordion>

  <Accordion title="500 — SERVER_ERROR">
    Error interno. Reintente con espera progresiva (2s, 4s, 8s).
  </Accordion>
</AccordionGroup>

<Tip>
  **Ninguna campaña vigente no es un error.** La respuesta llega con `campaigns: []` y estado 200 — la empresa simplemente puede no tener nada en el aire.
</Tip>

***

## Límites de uso

| Límite                                         | Valor |
| ---------------------------------------------- | ----- |
| Solicitudes por hora (predeterminado)          | 1000  |
| Máximo de colaboradores por `campaign_status`  | 500   |
| Máximo de colaboradores por `campaign_catalog` | 100   |

***

## Seguridad

* Solo lectura: esta API nunca crea canjes, ni acepta términos, ni altera saldos
* Cada clave pertenece a una sola empresa — un CPF de otra empresa responde `found: false`, y las campañas de otras empresas nunca aparecen
* Las solicitudes se firman con HMAC (P2S-SIGN-V1) y se registran para auditoría; los documentos nunca se escriben en los logs

<Warning>
  Nunca exponga su API Key en código del lado del cliente. Esta API debe llamarse únicamente desde su servidor.
</Warning>

***

## Próximos pasos

<CardGroup cols={2}>
  <Card title="API de Misiones" icon="bullseye-arrow" href="/es/api/integrations/missions">
    El progreso que genera los puntos gastados aquí
  </Card>

  <Card title="Autenticación" icon="key" href="/es/api/authentication">
    Cómo crear y gestionar API Keys
  </Card>
</CardGroup>
