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

# Autenticación

> Autentícate con la API de Integración de SalesOS usando API Keys con alcance por tenant.

# Autenticación

<Tip>
  Prueba peticiones firmadas en el navegador en el [Sandbox de la API](https://play2sellsa.github.io/api-sandbox/) — pega tu API key y secret, y el playground firma las peticiones automáticamente.
</Tip>

La API de Integración de SalesOS usa el esquema de peticiones firmadas **P2S-SIGN-V1** en el header `Authorization`. Las API Keys tienen alcance a un solo tenant, se hashean con bcrypt y soportan límites de tasa y listas de IPs permitidas.

| Esquema         | Cuándo usar                                                      | Formato                                   |
| --------------- | ---------------------------------------------------------------- | ----------------------------------------- |
| **P2S-SIGN-V1** | Llamadas server-to-server — firmadas con el secret de la API key | `P2S-SIGN-V1 API_KEY:TIMESTAMP:SIGNATURE` |

## Entornos

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

    **Dashboard:** `https://dashboard.play2sell.com`

    **App:** `https://app.play2sell.com`
  </Tab>

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

    **Dashboard:** `https://dashboard-staging.play2sell.com`

    **App:** `https://app-staging.play2sell.com`
  </Tab>
</Tabs>

## Inicio Rápido

### 1. Crear una API Key

Ve a **Integraciones > API Keys** en el Dashboard de SalesOS:

1. Haz clic en **Crear API Key**
2. Nombra tu clave (ej., "Sincronización Nocturna CRM", "Integración Formulario Web")
3. Selecciona el alcance: `default:sync`
4. Haz clic en **Crear**
5. **Copia los dos valores inmediatamente** — solo se muestran una vez:
   * **API Key** — identificador público, ej. `sk_live_a1b2c3d4...`
   * **API Key Secret** — usado para firmar peticiones, nunca se envía por la red

### 2. Firma y Envía una Petición

Para llamadas server-to-server, construye el header `Authorization` como `P2S-SIGN-V1 API_KEY:TIMESTAMP:SIGNATURE`. La `SIGNATURE` es el HMAC-SHA256 hex de una cadena de claves derivadas en 5 pasos:

1. `k1  = HMAC_SHA256(key=API_KEY_SECRET, msg=API_KEY)`
2. `k2  = HMAC_SHA256(key=k1, msg=TIMESTAMP)`
3. `k3  = HMAC_SHA256(key=k2, msg=METHOD)`
4. `k4  = HMAC_SHA256(key=k3, msg=PATH)`
5. `SIG = HMAC_SHA256_HEX(key=k4, msg=PAYLOAD_SHA256_HEX)`

`TIMESTAMP` es Unix epoch en segundos, válido por 30 segundos. `PAYLOAD_SHA256_HEX` es el SHA-256 hex en minúsculas del cuerpo bruto de la petición (usa el digest de la cadena vacía `e3b0c4...b855` cuando no haya body).

<Tabs>
  <Tab title="Node.js">
    ```js theme={null}
    import crypto from 'node:crypto';

    const API_KEY = process.env.SALESOS_API_KEY;
    const SECRET  = process.env.SALESOS_API_SECRET;

    const hmac = (key, msg) =>
      crypto.createHmac('sha256', key).update(msg).digest();

    async function signedRequest(method, path, body) {
      const ts          = Math.floor(Date.now() / 1000).toString();
      const bodyStr     = body ? JSON.stringify(body) : '';
      const payloadHash = crypto.createHash('sha256').update(bodyStr).digest('hex');

      const k1  = hmac(SECRET, API_KEY);
      const k2  = hmac(k1, ts);
      const k3  = hmac(k2, method);
      const k4  = hmac(k3, path);
      const sig = hmac(k4, payloadHash).toString('hex');

      const res = await fetch(`https://api.play2sell.com${path}`, {
        method,
        headers: {
          Authorization: `P2S-SIGN-V1 ${API_KEY}:${ts}:${sig}`,
          'Content-Type': 'application/json',
        },
        body: bodyStr || undefined,
      });
      return res.json();
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import hashlib, hmac, json, os, time, requests

    API_KEY = os.environ['SALESOS_API_KEY']
    SECRET  = os.environ['SALESOS_API_SECRET']

    def hmac_raw(key: bytes, msg: str) -> bytes:
        return hmac.new(key, msg.encode(), hashlib.sha256).digest()

    def signed_request(method: str, path: str, body=None):
        ts           = str(int(time.time()))
        body_str     = json.dumps(body) if body else ''
        payload_hash = hashlib.sha256(body_str.encode()).hexdigest()

        k1  = hmac_raw(SECRET.encode(), API_KEY)
        k2  = hmac_raw(k1, ts)
        k3  = hmac_raw(k2, method)
        k4  = hmac_raw(k3, path)
        sig = hmac_raw(k4, payload_hash).hex()

        return requests.request(
            method, f'https://api.play2sell.com{path}',
            headers={
                'Authorization': f'P2S-SIGN-V1 {API_KEY}:{ts}:{sig}',
                'Content-Type': 'application/json',
            },
            data=body_str or None,
        ).json()
    ```
  </Tab>

  <Tab title="Bash">
    ```bash theme={null}
    API_KEY="sk_live_..."
    SECRET="..."
    METHOD="POST"
    PATH_="/functions/v1/default-integration/sync_collaborators"
    BODY='{"action":"sync_collaborators","collaborators":[{"external_id":"emp-101","name":"Maria Santos","email":"maria@yourcompany.com"}]}'
    TS=$(date +%s)

    PAYLOAD=$(printf '%s' "$BODY"        | openssl dgst -sha256 -hex                                  | awk '{print $2}')
    K1=$(printf '%s'      "$API_KEY"     | openssl dgst -sha256 -hmac "$SECRET" -hex                  | awk '{print $2}')
    K2=$(printf '%s'      "$TS"          | openssl dgst -sha256 -mac HMAC -macopt "hexkey:$K1" -hex   | awk '{print $2}')
    K3=$(printf '%s'      "$METHOD"      | openssl dgst -sha256 -mac HMAC -macopt "hexkey:$K2" -hex   | awk '{print $2}')
    K4=$(printf '%s'      "$PATH_"       | openssl dgst -sha256 -mac HMAC -macopt "hexkey:$K3" -hex   | awk '{print $2}')
    SIG=$(printf '%s'     "$PAYLOAD"     | openssl dgst -sha256 -mac HMAC -macopt "hexkey:$K4" -hex   | awk '{print $2}')

    curl -X "$METHOD" "https://api.play2sell.com$PATH_" \
      -H "Authorization: P2S-SIGN-V1 $API_KEY:$TS:$SIG" \
      -H "Content-Type: application/json" \
      -d "$BODY"
    ```
  </Tab>
</Tabs>

<Tip>
  ¿No quieres escribir el código de firma todavía? El [Sandbox de la API](https://play2sellsa.github.io/api-sandbox/) firma las peticiones por ti en el navegador — pega tu API key y secret, luego haz clic en *Try it out*.
</Tip>

### 3. Verificar la Respuesta

**Éxito (200):**

```json theme={null}
{
  "data": { "created": 1, "existing": 0, "errors": [], "total": 1 },
  "meta": { "request_id": "f47ac10b-...", "timestamp": "2026-03-17T10:30:00.000Z" }
}
```

**Firma inválida o timestamp expirado (401):**

```json theme={null}
{
  "error": { "code": "UNAUTHORIZED", "message": "Invalid signature for P2S-SIGN-V1 request" }
}
```

***

## Propiedades de la API Key

| Propiedad                   | Detalles                                                           |
| --------------------------- | ------------------------------------------------------------------ |
| **Prefijo**                 | `sk_live_` (producción) o `sk_test_` (pruebas)                     |
| **Alcance**                 | `default:sync` — habilita `sync_collaborators` y `sync_activities` |
| **Límite de tasa**          | Configurable por clave (por defecto: 1000 solicitudes/hora)        |
| **Expiración**              | Opcional — establece una fecha de vencimiento o déjala sin expirar |
| **Lista de IPs permitidas** | Opcional — restringe a direcciones IP específicas                  |
| **Almacenamiento**          | Hasheada con bcrypt — la clave en texto plano nunca se almacena    |

***

## Formatos de Clave

SalesOS usa dos prefijos de clave para distinguir ambientes:

| Prefijo    | Ambiente   | Caso de uso                                                     |
| ---------- | ---------- | --------------------------------------------------------------- |
| `sk_live_` | Producción | Datos reales, misiones reales, puntos reales                    |
| `sk_test_` | Pruebas    | Seguro para usar durante desarrollo — sin impacto en producción |

<Tip>
  Usa claves `sk_test_` durante el desarrollo y las pruebas de integración. Cambia a `sk_live_` cuando pases a producción.
</Tip>

***

## Errores de Autenticación

| Estado HTTP | Código de Error | Significado                                           | Qué hacer                                                              |
| ----------- | --------------- | ----------------------------------------------------- | ---------------------------------------------------------------------- |
| 401         | `UNAUTHORIZED`  | La clave falta, es inválida o expiró                  | Verifica el header `Authorization`. Confirma la clave en el Dashboard. |
| 403         | `FORBIDDEN`     | La clave es válida pero no tiene el alcance requerido | Edita la clave y agrega el alcance `default:sync`                      |
| 429         | `RATE_LIMITED`  | Demasiadas solicitudes en esta hora                   | Espera `retry_after` segundos, luego reintenta                         |

### Ejemplo: Header Authorization faltante

```bash theme={null}
curl -X POST https://api.play2sell.com/functions/v1/default-integration \
  -H "Content-Type: application/json" \
  -d '{"action": "sync_collaborators", "collaborators": [...]}'
```

```json theme={null}
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Missing or malformed Authorization header"
  }
}
```

### Ejemplo: Firma inválida

Una firma que no coincide con la que recalcula el servidor — generalmente causada por un cambio en el body después de firmar, una divergencia en la canonicalización del path, o una clave desactualizada:

```json theme={null}
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid signature for P2S-SIGN-V1 request"
  }
}
```

### Ejemplo: Timestamp fuera de la ventana de 30s

```json theme={null}
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Request timestamp is outside the 30-second validity window"
  }
}
```

### Ejemplo: Clave sin el alcance requerido

Si tu clave solo tiene `leads:read` pero el endpoint requiere `default:sync`:

```json theme={null}
{
  "error": {
    "code": "FORBIDDEN",
    "message": "API key missing required scopes: default:sync"
  }
}
```

### Ejemplo: Límite de tasa excedido

```json theme={null}
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded",
    "retry_after": 1847
  }
}
```

El campo `retry_after` indica cuántos segundos debes esperar. La ventana de límite de tasa se reinicia cada hora.

***

## Límites de Tasa

Cada API key tiene un contador de límite de tasa independiente que se reinicia cada hora:

| Configuración        | Por defecto | Rango       |
| -------------------- | ----------- | ----------- |
| Solicitudes por hora | 1000        | 1 — 100,000 |

**Cómo funciona:**

1. Cada solicitud exitosa incrementa el contador
2. Cuando el contador alcanza el límite, las solicitudes siguientes retornan `429`
3. El contador se reinicia a 0 una hora después de la primera solicitud en la ventana

**Manejo de límites de tasa en código:**

```javascript theme={null}
// signedRequest() es el helper del Inicio Rápido de arriba.
async function callWithRetry(method, path, body) {
  const response = await signedRequest(method, path, body);

  if (response.status === 429) {
    const { error } = await response.json();
    const waitSeconds = error.retry_after || 60;
    console.log(`Rate limited. Retrying in ${waitSeconds}s...`);
    await new Promise(r => setTimeout(r, waitSeconds * 1000));
    return callWithRetry(method, path, body); // retry
  }

  return response.json();
}
```

***

## Mejores Prácticas de Seguridad

<Warning>
  **Nunca expongas API keys en código del lado del cliente.** JavaScript en el navegador, aplicaciones móviles y repositorios públicos pueden filtrar tu clave. Siempre llama a la API de SalesOS desde tu servidor backend.
</Warning>

* **Usa variables de entorno** — Almacena `SALESOS_API_KEY` en variables de entorno o un gestor de secretos, nunca en el código fuente
* **Rota las claves periódicamente** — Crea una nueva clave, actualiza tu integración, luego revoca la anterior
* **Usa listas de IPs permitidas** — Si tu integración se ejecuta desde IPs fijas, restringe la clave solo a esas IPs
* **Monitorea el uso** — Revisa los logs de uso de la API en el Dashboard para detectar patrones inesperados
* **Usa `sk_test_` para desarrollo** — Las claves de prueba aíslan tu ambiente de desarrollo de producción
* **Revoca claves comprometidas inmediatamente** — Ve a Dashboard > Admin > API Keys > Revocar

### Ejemplo de rotación de claves

```bash theme={null}
# 1. Crea una nueva clave en el Dashboard → copia ambos valores:
#    - SALESOS_API_KEY=sk_live_NEW_KEY
#    - SALESOS_API_SECRET=NEW_SECRET
# 2. Actualiza ambas variables de entorno en tu deployment.
# 3. Verifica que funciona usando el helper de petición firmada del Inicio
#    Rápido (Node, Python o Bash). Payload de ejemplo:
#    { "action": "sync_collaborators",
#      "collaborators": [{"external_id":"test","name":"Test","email":"test@co.com"}] }
# 4. Revoca la clave antigua en el Dashboard.
```

***

## Proximos Pasos

<CardGroup cols={2}>
  <Card title="Integracion por Defecto" icon="plug" href="/es/api/integrations/default">
    Comienza a enviar actividades a SalesOS
  </Card>

  <Card title="API Keys" icon="key" href="/es/api/integrations/api-keys">
    Gestiona claves programaticamente
  </Card>
</CardGroup>
