---
seo_title: Autenticación con clave API: X-API-Key y JWT | EnConvert
meta_desc: EnConvert tiene dos tipos de clave: privadas sk_ para servidores y públicas pk_ que se cambian por un JWT en el navegador, más GET /v1/auth/verify para validar.
keywords: autenticación con encabezado x-api-key, token jwt bearer para api, diferencia entre claves sk_ y pk_, intercambiar clave pública por jwt, endpoint /v1/auth/token, verificar si mi api key es válida, rotar clave api sin downtime, lista blanca de dominios para api key, error 403 forbidden allowed_endpoints, autenticación api del lado del cliente
---

# Autenticación

EnConvert tiene dos tipos de clave de API, y el lugar donde se ejecuta tu código decide cuál usas. Una clave privada (`sk_`) va en el encabezado `X-API-Key` desde un servidor que tú controlas; una clave pública (`pk_`) se intercambia por un JWT de corta duración que el código del navegador envía como `Authorization: Bearer <token>`.

---

## Cómo elegir el tipo de clave

| | Clave privada | Clave pública + JWT |
|---|---|---|
| Prefijo | `sk_` | `pk_` |
| Se envía como | `X-API-Key: sk_your_private_key` | `Authorization: Bearer <token>` |
| Se ejecuta en | servidores, scripts, trabajos de CI, contenedores | navegadores, widgets embebidos, cualquier cosa que se entregue a un cliente |
| Se rechaza cuando | la solicitud lleva un encabezado `Origin` (`403`) | llama a algo que no sea `/v1/auth/token` o `/v1/auth/branding` (`403`) |
| Alcance | todos los endpoints: síncronos, asíncronos, por lotes, webhooks | un elemento por solicitud, síncrono, descarga con URL prefirmada |
| Restricciones | `allowed_endpoints` opcional | `allowed_domains` más `allowed_endpoints` |
| Vigencia | la clave vale hasta que se revoca | la clave vale hasta que se revoca, el token de acceso 1 hora, la cookie de refresh 7 días |

Elige según el destino de despliegue. Un servicio backend, un script, un trabajo cron o una herramienta interna usa una clave privada. Una aplicación de navegador o un widget embebido usa una clave pública con JWT. No hay forma de ocultar una clave privada en el código del frontend: la pasarela la rechaza por la sola presencia de un encabezado `Origin`, antes de comprobar cualquier otra cosa.

Una clave es su prefijo seguido de un token aleatorio, 46 caracteres en total. Los nombres de marcador de posición de los ejemplos siguientes (`sk_your_private_key`, `pk_your_public_key`) ocupan el lugar de ese token; una clave real no lleva dentro ningún segmento de entorno como `live` o `test`. Cualquier cosa de menos de 45 caracteres se rechaza con `401 Invalid API Key format` antes incluso de leer el prefijo.

En el servidor solo se almacena un hash SHA-256 de cada clave, junto con un fragmento de prefijo de siete caracteres para que puedas distinguir tus claves en el panel.

---

## Claves privadas {: #private-keys }

Las claves privadas están pensadas para **aplicaciones del lado del servidor** donde tu clave de API puede mantenerse en secreto. Proporcionan acceso completo a todos los endpoints y funciones de la API.

- **Encabezado:** `X-API-Key: sk_your_private_key`
- **Acceso:** Acceso completo a todos los endpoints, incluidas operaciones síncronas y asíncronas, procesamiento por lotes y todos los tipos de conversión.
- **Seguridad:** Las claves se almacenan en el servidor como hashes SHA-256. La clave en texto plano se muestra solo una vez, en el momento de su creación.

No se requiere intercambio de tokens ni gestión de sesiones. Incluye la clave en cada solicitud:

```bash
curl -X POST https://api.enconvert.com/v1/convert/url-to-pdf \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}'
```

### Formato del encabezado

Incluye tu clave privada en el encabezado `X-API-Key` en cada solicitud:

```http
X-API-Key: sk_your_private_key
```

Las claves privadas siempre comienzan con el prefijo `sk_`. Puedes generar y gestionar tus claves desde el panel de EnConvert.

### Ejemplo: conversión de archivo

Convierte un archivo JSON a XML usando una clave privada:

```bash
curl -X POST https://api.enconvert.com/v1/convert/json-to-xml \
  -H "X-API-Key: sk_your_private_key" \
  -F "file=@data.json"
```

Respuesta:

```json
{
  "presigned_url": "https://econverter.nyc3.cdn.digitaloceanspaces.com/...",
  "object_key": "live/files/12345/json-to-xml/data_20250202_120530123.xml",
  "filename": "data_20250202_120530123.xml",
  "file_size": 1024,
  "conversion_time_seconds": 0.45
}
```

- `presigned_url`: una URL temporal y descargable para obtener el archivo convertido.
- `object_key`: la ruta de almacenamiento del archivo convertido (por ejemplo, `live/files/12345/json-to-xml/...`). No es una URL.
- `filename`: el nombre de archivo generado para el archivo convertido.
- `file_size`: el tamaño del archivo de salida en bytes.
- `conversion_time_seconds`: el tiempo empleado en completar la conversión.

Esas URL de descarga son de corta duración. Sus reglas de caducidad y retención están en [URL firmadas](/es/docs/concepts/signed-urls.md).

### Restricciones de endpoints

De forma predeterminada, una clave privada tiene acceso a todos los endpoints de la API. Opcionalmente, puedes restringir una clave a endpoints específicos usando el ajuste **allowed_endpoints** en el momento de crearla.

Cuando se configura `allowed_endpoints`, la clave solo podrá llamar a los endpoints indicados. Las solicitudes a cualquier otro endpoint se rechazarán con un error `403 Forbidden`: `Endpoint '{path}' not allowed for this API key`.

Ejemplo de configuración:

```json
{
  "allowed_endpoints": [
    "/v1/convert/url-to-pdf",
    "/v1/convert/json-to-xml",
    "/v1/convert/html-to-pdf"
  ]
}
```

Esto es útil cuando quieres emitir una clave con alcance limitado, por ejemplo, una clave que solo pueda realizar conversiones de PDF.

Unas cuantas rutas siguen siendo accesibles diga lo que diga la lista, porque una clave que puede iniciar un trabajo tiene que poder terminarlo:

- `/v1/auth/token`, `/v1/auth/verify` y `/v1/whoami`
- `/v1/convert/status/{job_id}`, `/v1/convert/batch/{batch_id}` y `/v1/convert/download/{object_key}`
- `/v1/extension/*`, para solicitudes autenticadas con JWT
- las rutas V2 por trabajo de perceive, ingest y watch

Una clave creada con la única entrada `["*"]` significa todos los endpoints, incluidos los que se publiquen después de crear la clave.

La lista queda fija en el momento de la creación. No existe ninguna llamada que edite las restricciones de una clave ya existente, así que reducir o ampliar el alcance de una clave implica crear una clave nueva y revocar la antigua. Consulta [Cómo mantener tus claves a salvo](#keeping-keys-safe).

<div class="alert alert-warning">
<strong>No uses claves privadas en código del lado del cliente.</strong> La API detecta el encabezado <code>Origin</code> enviado por los navegadores y rechazará las solicitudes hechas con una clave privada desde un entorno de navegador con <code>403 Private API keys cannot be used from browsers</code>. Para integraciones del lado del cliente, usa una <a href="#public-keys-and-jwt">clave pública con JWT</a> en su lugar.
</div>

---

## Claves públicas y JWT {: #public-keys-and-jwt }

La autenticación JWT con clave pública permite que las apps del lado del cliente (navegador) llamen a la API de EnConvert: intercambias tu clave pública (`pk_`) por un token de acceso JWT de corta duración mediante `POST /v1/auth/token`, y luego envías ese token en el encabezado `Authorization: Bearer <token>` en las solicitudes a la API. Como una clave pública es visible para los usuarios finales, no puede llamar a la API directamente. Por sí sola alcanza exactamente dos rutas, `/v1/auth/token` y `/v1/auth/branding`. Todo lo demás devuelve `403` con un mensaje que te indica que primero intercambies la clave por un token.

1. **Intercambia** tu clave pública (`pk_`) por un token de acceso JWT llamando a `POST /v1/auth/token`.
2. **Usa** el token JWT en el encabezado `Authorization: Bearer <token>` en las solicitudes a la API.
3. **Renueva** el token automáticamente antes de que caduque usando `POST /v1/auth/refresh`.
4. La **lista blanca de dominios** garantiza que solo se acepten solicitudes originadas desde tus dominios aprobados.

### Paso 1: intercambiar la clave pública por un JWT

```http
POST /v1/auth/token
```

| Encabezado | Valor | Descripción |
|---|---|---|
| `X-API-Key` | `pk_your_public_key` | Tu clave de API pública |

El endpoint espera un objeto JSON en el cuerpo. Si no envías ningún cuerpo, devuelve `422` con `{"type":"missing","loc":["body"],"msg":"Field required"}`, así que envía `{}` cuando no tengas nada que pasar. El único campo opcional es `turnstile_token`, que solo se verifica en las solicitudes que provienen del origen del propio widget de EnConvert y se ignora en todos los demás casos.

```javascript
async function getToken() {
  const response = await fetch("https://api.enconvert.com/v1/auth/token", {
    method: "POST",
    headers: {
      "X-API-Key": "pk_your_public_key",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({}),
    credentials: "include",
  });

  if (!response.ok) {
    throw new Error(`Token exchange failed: ${response.status}`);
  }

  const data = await response.json();
  return data.token;
}
```

<div class="alert alert-warning">
<strong>Importante:</strong> Debes incluir <code>credentials: "include"</code> en las opciones de fetch. Esto garantiza que el navegador almacene la cookie del refresh token, algo necesario para la renovación automática del token.
</div>

Respuesta:

```json
{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600
}
```

La respuesta también establece una cookie `HttpOnly` que contiene el refresh token. Esta cookie es gestionada automáticamente por el navegador y se usa al renovar el token de acceso.

Una clave privada enviada a este endpoint se rechaza con `400 Only public API keys can exchange for tokens. Private keys should be used directly.` Eso es la API diciéndote que te saltes el paso de intercambio, no una clave rota.

### Paso 2: usar el token JWT

Incluye el token JWT en el encabezado `Authorization` como un token Bearer en todas las solicitudes posteriores a la API.

```javascript
async function convertUrlToPdf(token, url) {
  const response = await fetch("https://api.enconvert.com/v1/convert/url-to-pdf", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ url }),
  });

  return await response.json();
}

// Usage
const token = await getToken();
const result = await convertUrlToPdf(token, "https://example.com");
console.log(result.presigned_url);
```

### Paso 3: renovación automática del token

Los tokens de acceso caducan al cabo de una hora. Usa el endpoint de renovación para obtener un nuevo token de acceso sin requerir que el usuario vuelva a autenticarse.

```http
POST /v1/auth/refresh
```

El refresh token se envía automáticamente mediante la cookie `HttpOnly` que se estableció durante el intercambio inicial del token. No se necesita cuerpo de solicitud ni encabezados adicionales.

```javascript
class EnconvertClient {
  constructor(publicKey) {
    this.publicKey = publicKey;
    this.token = null;
    this.tokenExpiry = null;
  }

  async getToken() {
    const response = await fetch("https://api.enconvert.com/v1/auth/token", {
      method: "POST",
      headers: {
        "X-API-Key": this.publicKey,
        "X-Parent-Origin": window.location.origin,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({}),
      credentials: "include",
    });

    const data = await response.json();
    this.token = data.token;
    // Set expiry to 55 minutes (refresh before the 1-hour expiry)
    this.tokenExpiry = Date.now() + 55 * 60 * 1000;
    return this.token;
  }

  async refreshToken() {
    const response = await fetch("https://api.enconvert.com/v1/auth/refresh", {
      method: "POST",
      credentials: "include",
    });

    if (!response.ok) {
      // Refresh token expired, re-authenticate
      return await this.getToken();
    }

    const data = await response.json();
    this.token = data.token;
    this.tokenExpiry = Date.now() + 55 * 60 * 1000;
    return this.token;
  }

  async getValidToken() {
    if (!this.token || Date.now() >= this.tokenExpiry) {
      if (this.token) {
        return await this.refreshToken();
      }
      return await this.getToken();
    }
    return this.token;
  }

  async convert(endpoint, body) {
    const token = await this.getValidToken();
    const response = await fetch(`https://api.enconvert.com${endpoint}`, {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });

    return await response.json();
  }
}

// Usage
const client = new EnconvertClient("pk_your_public_key");
const result = await client.convert("/v1/convert/url-to-pdf", {
  url: "https://example.com",
});
```

Cuatro cosas que conviene saber antes de depurar una renovación que falla:

- La renovación resuelve tu proyecto a partir de la cookie y luego busca cualquier clave pública activa en ese proyecto. Revoca todas las claves públicas y la renovación empezará a devolver `401`, incluso mientras la cookie siga dentro de sus siete días.
- La ruta de renovación no vuelve a comprobar la lista blanca de dominios. Esa comprobación ocurre cuando se emite el token.
- La renovación no vuelve a vincular el nuevo token con la clave que usaste originalmente. Si el proyecto tiene varias claves públicas, el token renovado puede llegar con las restricciones de otra clave.
- La emisión y la renovación de tokens tienen su propio límite por IP, independiente de los [límites de velocidad](/es/docs/reference/rate-limits.md) de tu plan. Un cliente atrapado en un bucle de renovación lo va a notar.

### Duración de los tokens

| Token | Duración | Almacenamiento |
|---|---|---|
| Token de acceso | 1 hora | Se devuelve en el cuerpo de la respuesta JSON; almacénalo en memoria |
| Refresh token | 7 días | Se establece como cookie `HttpOnly`; gestionada por el navegador |

### Lista blanca de dominios

Las claves públicas están restringidas a dominios específicos configurados en tu panel.

La coincidencia compara solo el host y el puerto. Primero se elimina el esquema de ambos lados, así que `https://example.com` y `http://example.com` son el mismo origen a efectos de la lista blanca. El puerto no se elimina y forma parte de la coincidencia.

- **Coincidencia exacta:** `https://example.com` coincide con el host desnudo `example.com` en cualquier esquema.
- **Subdominios comodín:** `https://*.example.com` coincide con `https://app.example.com`, `https://staging.example.com` y también con el ápice `https://example.com`.
- **Específico de puerto:** `http://localhost:3000` coincide solo con ese host y ese puerto.

| Entrada de la lista blanca | Coincide con | No coincide con |
|---|---|---|
| `https://example.com` | `https://example.com`, `http://example.com` | `https://www.example.com` |
| `https://*.example.com` | `https://app.example.com`, `https://dev.example.com`, `https://example.com` | `https://example.net` |
| `http://localhost:3000` | `http://localhost:3000` | `http://localhost:8080` |

Una solicitud desde un origen que no está en la lista recibe `403 Domain {origin} not authorized`, y se avisa por correo al propietario del proyecto (como mucho una vez por clave cada 24 horas). Si tu bandeja de entrada se está llenando, la causa habitual es una entrada obsoleta en la lista.

Dos orígenes se saltan la comprobación de dominio por completo: un origen `chrome-extension://...`, para que las extensiones de navegador puedan llamar a la API, y el origen del propio widget de EnConvert, donde la comprobación pasa a ser la validación de `X-Parent-Origin` del widget.

### Funciones de seguridad

- **Tokens de corta duración:** Los tokens de acceso caducan después de 1 hora, lo que limita la ventana de exposición si un token se ve comprometido.
- **Cookies de refresh HttpOnly:** Los refresh tokens se almacenan en cookies `HttpOnly`, lo que los hace inaccesibles para JavaScript y resistentes a ataques XSS.
- **Restricciones de dominio:** Los tokens solo se emiten cuando la solicitud se origina desde un dominio en la lista blanca.
- **Sin acceso directo a la API:** Las claves públicas por sí solas no pueden llamar a los endpoints de conversión. Siempre se requiere un JWT válido.

### Restricciones de la clave pública

La autenticación con clave pública tiene las siguientes limitaciones en comparación con las claves privadas:

- **Solo síncrono:** Solo están disponibles los endpoints de conversión síncronos. El modo asíncrono y las devoluciones de llamada webhook no lo están; `notification_email` y `callback_url` se borran en las conversiones hechas con clave de navegador.
- **Un elemento por solicitud:** Cada solicitud puede convertir solo una URL o un archivo. Enviar un array devuelve `400 Public keys only support a single URL input`.
- **Descarga directa:** Las respuestas proporcionan una `presigned_url` para descarga inmediata. No hay opción de destinos de almacenamiento personalizados.
- **Estado de trabajo sí, estado de lote no:** `GET /v1/convert/status/{job_id}` funciona con un token de navegador, que es como el widget recupera un resultado tras una conexión caída. `GET /v1/convert/batch/{batch_id}` se rechaza con `403 Batch status requires a private API key`.

El envío por lotes en sí lo limita el límite de lotes de tu plan, no el tipo de clave, pero como una clave de navegador está limitada a un elemento por solicitud, en la práctica los lotes necesitan una clave privada. Consulta [Procesamiento por lotes](/es/docs/guides/batch-processing.md).

<div class="alert alert-warning">
<strong>Buenas prácticas:</strong>
<ul>
  <li>Almacena siempre los tokens de acceso solo en memoria. Nunca los persistas en <code>localStorage</code> o <code>sessionStorage</code>.</li>
  <li>Implementa la renovación automática de tokens para evitar interrupciones durante las sesiones de usuario.</li>
  <li>Mantén tu lista de dominios en lista blanca lo más específica posible. Evita comodines amplios.</li>
  <li>Usa <code>credentials: "include"</code> en todas las solicitudes fetch para garantizar que las cookies se envíen y reciban correctamente.</li>
  <li>Gestiona con elegancia los fallos de renovación de token recurriendo a una reautenticación completa con la clave pública.</li>
</ul>
</div>

Si quieres el flujo del navegador sin escribir nada de esto, el widget embebible emite y renueva sus propios tokens. Consulta [Widgets web](/es/docs/guides/integrations.md#web-widgets).

---

## Verifica tus credenciales {: #verify-your-credentials }

`GET /v1/auth/verify` comprueba si tu autenticación actual es válida e informa de qué cree la API que es. Funciona con claves privadas enviadas en el encabezado `X-API-Key` y con tokens JWT bearer enviados en el encabezado `Authorization`. Una solicitud válida devuelve tu `project_id`, `tier`, `key_type` y cualquier restricción de dominio o endpoint; una clave o token inválido o caducado devuelve `401 Unauthorized`.

```http
GET /v1/auth/verify
```

| Encabezado | Valor | Descripción |
|---|---|---|
| `X-API-Key` | `sk_your_private_key` | Autentica con una clave privada |
| `Authorization` | `Bearer <token>` | Autentica con un token JWT |

Usa uno de los dos encabezados anteriores, no ambos.

<div class="alert alert-warning">
<strong>No puedes verificar una clave pública directamente.</strong> Enviar <code>X-API-Key: pk_...</code> a este endpoint devuelve <code>403</code>, porque una clave pública solo puede llamar a <code>/v1/auth/token</code> y <code>/v1/auth/branding</code>. Emite primero un token y verifica después ese token. Este es el único caso en el que un <code>403</code> aquí no significa que tu clave esté rota.
</div>

Con una clave privada:

```bash
curl https://api.enconvert.com/v1/auth/verify \
  -H "X-API-Key: sk_your_private_key"
```

Con un token JWT:

```bash
curl https://api.enconvert.com/v1/auth/verify \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
```

Respuesta:

```json
{
  "authenticated": true,
  "project_id": "12345",
  "tier": "pro",
  "key_type": "public",
  "allowed_domains": ["https://example.com", "https://*.example.com"],
  "allowed_endpoints": ["/v1/convert/url-to-pdf", "/v1/convert/jpeg-to-png"]
}
```

| Campo | Tipo | Descripción |
|---|---|---|
| `authenticated` | boolean | Siempre `true` para una solicitud válida |
| `project_id` | string | El ID de tu proyecto |
| `tier` | string | Tu nivel de suscripción (por ejemplo, `free`, `starter`, `pro`, `business`) |
| `key_type` | string | `private`, `public` o `dashboard` |
| `allowed_domains` | array o null | Dominios en lista blanca (solo claves públicas; `null` en los demás casos) |
| `allowed_endpoints` | array o null | Endpoints restringidos (solo claves públicas; `null` en los demás casos) |

Dos detalles que suelen despistar. `key_type` tiene un tercer valor, `dashboard`, que es lo que el backend emite para una sesión de panel o de playground con la sesión iniciada; igual que una clave privada, devuelve ambas listas como `null`. Y `tier` es el slug del plan, no el nombre que aparece en la página de precios: una suscripción Studio devuelve `"tier": "pro"`. Los slugs `free`, `starter`, `pro`, `business` y `enterprise` corresponden a Founding, Indie, Studio, Production y Enterprise.

Si la clave o el token es inválido o ha caducado, la API devuelve un error `401 Unauthorized` en su lugar. La lista completa de mensajes de error de autenticación está en [Errores](/es/docs/reference/errors.md).

### GET /v1/whoami

Hay un segundo endpoint de identidad, más pequeño. Requiere una clave privada:

```bash
curl https://api.enconvert.com/v1/whoami \
  -H "X-API-Key: sk_your_private_key"
```

```json
{
  "project_id": "12345",
  "plan_slug": "pro"
}
```

No devuelve nada más a propósito: ni tipo de clave, ni dominios, ni límites. Un JWT o una clave pública recibe `403 GET /v1/whoami requires a private API key (sk_...).` Varias integraciones lo usan como prueba de credenciales, incluido el nodo de n8n.

### Casos de uso

- **Probar claves de API:** Confirma que una clave recién creada está activa y correctamente configurada.
- **Comprobar restricciones de dominio:** Verifica qué dominios están en la lista blanca para una clave pública.
- **Depurar problemas de autenticación:** Determina si un fallo en la solicitud se debe a la autenticación o a otra causa.

---

## Cómo mantener tus claves a salvo {: #keeping-keys-safe }

- **Almacenamiento con hash:** Las claves privadas se almacenan en el servidor como hashes SHA-256. La clave en texto plano se muestra solo una vez, en el momento de la creación. Si la pierdes, debes generar una nueva clave.
- **Variables de entorno:** Guarda tu clave en una variable de entorno (por ejemplo, `ENCONVERT_API_KEY`) en lugar de codificarla directamente en tu código fuente.
- **Alcance al crearla:** `allowed_endpoints` y `allowed_domains` se definen al crear la clave y no se pueden editar después. Decide el alcance antes de pulsar crear.

### Rotar una clave

La rotación consiste en crear y luego revocar, y en este orden no cuesta nada de tiempo de inactividad:

1. Crea la nueva clave en el panel con el alcance que quieras.
2. Despliégala y confirma después que la nueva clave está activa con `GET /v1/auth/verify`.
3. Revoca la clave antigua.

Ambas claves funcionan durante el paso 2, así que no hay ninguna ventana en la que tu servicio se quede sin autenticar. Como las restricciones son inmutables, cambiar el alcance de una clave es el mismo procedimiento que rotarla.

### Si una clave se filtra

Revócala primero y calcula después el alcance del daño. Revocar es el único interruptor de emergencia, porque el alcance de una clave activa no se puede reducir; una clave revocada se rechaza con `401 API Key revoked`.

- Una **clave privada** filtrada puede llamar a todos los endpoints incluidos en su alcance y gasta tus operaciones mensuales. Revócala, crea una sustituta y revisa tu uso en el panel en busca de llamadas que no hiciste.
- Una **clave pública** filtrada es menos urgente por diseño. No puede llamar en absoluto a los endpoints de conversión, y solo emite tokens para orígenes que estén en su lista blanca. Ajustar esa lista implica crear una clave más restringida y revocar la filtrada, ya que la lista de una clave existente no se puede editar.
- Un **token de acceso** filtrado muere antes de una hora y no se puede usar desde un origen distinto de aquel para el que se emitió. Su cookie de refresh es el problema más duradero: la renovación funciona mientras el proyecto tenga cualquier clave pública activa, así que revocar la clave de la que salió el token no anula la cookie a menos que fuera tu última clave pública.

<div class="alert alert-warning">
<strong>Una clave subida a git ya es pública.</strong> Revócala en el panel antes de reescribir el historial. Borrar el commit no despublica la clave.
</div>

---

## Preguntas frecuentes

### ¿Cómo me autentico en una API REST con un encabezado X-API-Key?

Envía tu clave privada en el encabezado `X-API-Key` en cada solicitud, por ejemplo `X-API-Key: sk_your_private_key`. Las claves privadas otorgan acceso completo a todos los endpoints (incluidas operaciones síncronas y asíncronas, procesamiento por lotes y todos los tipos de conversión) sin necesidad de intercambio de tokens.

### ¿Cuál es la diferencia entre las claves de API sk_ y pk_?

Las claves con el prefijo `sk_` son claves privadas para uso servidor a servidor y proporcionan acceso completo a la API mediante el encabezado `X-API-Key`. Las claves con el prefijo `pk_` son claves públicas para aplicaciones del lado del cliente (navegador): no pueden llamar a la API directamente y primero deben intercambiarse por un JWT de corta duración mediante `POST /v1/auth/token`.

### ¿Puedo usar mi clave privada de API (sk_) en un navegador o una aplicación móvil?

No. La API detecta el encabezado `Origin` enviado por los navegadores y rechaza las solicitudes realizadas con claves privadas desde entornos de navegador con `403 Private API keys cannot be used from browsers`. Usa una clave pública (`pk_`) con el flujo JWT para integraciones del lado del cliente en su lugar.

### ¿Cómo obtengo un token JWT Bearer para autenticación de API del lado del cliente?

Intercambia tu clave pública (`pk_`) por un JWT llamando a `POST /v1/auth/token` con la clave en el encabezado `X-API-Key` y `{}` como cuerpo JSON. Usa el token devuelto en el encabezado `Authorization: Bearer <token>` en las solicitudes a la API, y renuévalo antes de que caduque mediante `POST /v1/auth/refresh`.

### ¿Puedo restringir una clave de API privada a endpoints específicos?

Sí. Configura `allowed_endpoints` al crear la clave, indicando rutas como `/v1/convert/url-to-pdf`. Las solicitudes a cualquier endpoint que no esté en la lista se rechazan con un error `403 Forbidden`, salvo las rutas de autenticación, estado, descarga y las rutas por trabajo, que siguen siendo accesibles para todas las claves.

### ¿Qué ocurre si pierdo mi clave de API privada?

Las claves privadas se almacenan en el servidor como hashes SHA-256, y la clave en texto plano se muestra solo una vez, en el momento de la creación. Si la pierdes, debes generar una nueva clave. Puedes crear varias claves y revocar las antiguas desde el panel sin tiempo de inactividad.

### ¿Cuánto duran los tokens de acceso y los refresh tokens?

Los tokens de acceso caducan después de 1 hora y deben almacenarse solo en memoria. Los refresh tokens duran 7 días y se establecen como una cookie `HttpOnly` gestionada por el navegador.

### ¿Por qué falla la renovación de mi token sin credentials: "include"?

El refresh token se almacena en una cookie `HttpOnly` establecida durante el intercambio inicial del token, y `POST /v1/auth/refresh` depende de que el navegador envíe esa cookie automáticamente. Si omites `credentials: "include"` en tus solicitudes fetch, la cookie no se almacena ni se envía. Cuando falla una renovación, recurre a una reautenticación completa con tu clave pública.

### ¿Puedo usar subdominios comodín en la lista blanca de dominios?

Sí. `https://*.example.com` coincide con `https://app.example.com`, `https://staging.example.com` y también con el ápice `https://example.com`. También se admiten hosts exactos y orígenes específicos de puerto como `http://localhost:3000`. La coincidencia ignora el esquema, pero no el puerto.

### ¿Puedo usar una clave pública para conversiones asíncronas o por lotes?

No. La autenticación con clave pública solo admite endpoints de conversión síncronos, con una única URL o archivo por solicitud; el modo asíncrono, los webhooks y el sondeo del estado de lotes requieren una clave privada. Las respuestas proporcionan una `presigned_url` para descarga inmediata.

### ¿Cómo compruebo si mi clave de API es válida?

Envía una solicitud a `GET /v1/auth/verify` con una clave privada en el encabezado `X-API-Key`, o con un JWT en el encabezado `Authorization`. Una credencial válida devuelve `authenticated: true` junto con tu `project_id` y tu `tier`; una inválida o caducada devuelve `401 Unauthorized`. Una clave pública no se puede verificar así y devuelve `403`.

### ¿Por qué allowed_domains y allowed_endpoints son null en la respuesta de verify?

Ambos campos se completan solo para claves públicas y devuelven `null` para claves privadas y para sesiones de panel. Para claves públicas, `allowed_domains` lista los dominios en lista blanca y `allowed_endpoints` lista cualquier restricción de endpoint.

### ¿Qué método de autenticación debo elegir para mi integración?

Usa una clave privada (`sk_`) para servicios backend, scripts o herramientas internas: es más simple y otorga acceso completo. Usa una clave pública (`pk_`) con JWT para aplicaciones o widgets basados en navegador, ya que mantiene las credenciales seguras y restringe el acceso a dominios en lista blanca.
