> ## Documentation Index
> Fetch the complete documentation index at: https://docs.craqui.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tokens

> Cuánto dura cada token, cómo se renueva el access token con un refresh token que rota, qué hacer ante invalid_grant y cómo desconectar un negocio.

Cada conexión de tu app con un negocio tiene dos tokens:

| Token | Empieza con | Dura | Para qué |
| - | - | - | - |
| **Access token** | `crq_oat_` | 1 hora | Llamar a la API: `Authorization: Bearer crq_oat_…` |
| **Refresh token** | `crq_ort_` | 90 días sin uso | Pedir un access token nuevo en `/oauth/token`. Cambia en cada uso |

La **conexión** no tiene vencimiento: dura hasta que el propietario la desconecta, tu app la revoca o Craqui la
desconecta por seguridad. Si Craqui suspende tu app, la conexión queda en pausa y vuelve a funcionar cuando la
reactivamos. Mientras tu app use el refresh token al menos una vez cada 90 días, la conexión sigue viva.

Los dos tokens son opacos: no intentes leer nada de ellos. Para saber de qué negocio es un par, guárdalo junto al
`business_id` que vino en la respuesta de `/oauth/token`.

## Renueva el access token

Cuando el access token vence, la API responde `401` [`invalid_access_token`](https://docs.craqui.com/errors/invalid_access_token). No
esperes a eso: renuévalo un poco antes de la hora, con el refresh token de ese negocio.

```bash theme={null}
curl -s https://api.craqui.com/oauth/token \
  -u "$CRAQUI_CLIENT_ID:$CRAQUI_CLIENT_SECRET" \
  -d grant_type=refresh_token \
  -d refresh_token="$CRAQUI_REFRESH_TOKEN"
```

```json theme={null}
{
  "access_token": "crq_oat_TU_ACCESS_TOKEN_NUEVO",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "crq_ort_TU_REFRESH_TOKEN_NUEVO",
  "scope": "business:read clients:read payments:read",
  "business_id": "biz_01jtm0r4w8a3b6c9d2e5f7g1hk"
}
```

### El refresh token rota en cada uso

Cada renovación te devuelve un **refresh token nuevo**, y el que usaste deja de servir. Por eso:

1. **Guarda el refresh token nuevo antes de usar el access token nuevo**, en la misma operación en tu base de
   datos. Si tu proceso se cae entre una cosa y la otra y te quedas con el refresh viejo, puedes perder la
   conexión (salvo dentro del margen de 60 segundos de abajo).
2. **Un solo renovador por negocio.** Si dos procesos de tu app renuevan a la vez el mismo refresh token, uno
   de los dos se queda con tokens que ya no sirven y, cuando presenta ese refresh, se trata como un robo y la
   conexión se corta (abajo). Serializa la renovación por
   `business_id`: un lock en tu base de datos, una cola, o un único proceso que renueva.
3. **No guardes tokens viejos.** Un refresh que ya usaste no te sirve para nada, salvo en el margen de abajo.

Con `oauth4webapi`, siguiendo el ejemplo de [Conectar un negocio](https://docs.craqui.com/apps/authorization-code.md):

```ts theme={null}
export async function refresh(businessId: string) {
  // Tu lock por negocio: nadie más renueva este refresh token mientras tanto.
  return withBusinessLock(businessId, async () => {
    const current = await loadConnection(businessId);
    const response = await oauth.refreshTokenGrantRequest(as, client, clientAuth, current.refreshToken);
    const tokens = await oauth.processRefreshTokenResponse(as, client, response);
    // El refresh nuevo se guarda antes de usar el access nuevo.
    await saveConnection({
      businessId,
      accessToken: tokens.access_token,
      accessExpiresAt: Date.now() + (tokens.expires_in ?? 3600) * 1000,
      refreshToken: tokens.refresh_token!,
      scopes: (tokens.scope ?? '').split(' '),
    });
    return tokens.access_token;
  });
}
```

### Si la respuesta se perdió: el margen de 60 segundos

Si mandaste la renovación y no te llegó la respuesta (se cortó la red, se cayó tu proceso), puedes repetirla
con **el mismo refresh token**. Craqui la acepta si pasa todo esto:

* pasaron menos de **60 segundos** desde la renovación anterior;
* el access token que te dio esa renovación **nunca se usó** en la API.

Te devuelve un par nuevo, y el par que se perdió deja de funcionar. Fuera de ese margen, presentar un refresh
que ya se usó es **reutilización**.

### Reutilización: la conexión se corta

Si llega un refresh token que ya se había cambiado por otro, y no es el reintento de arriba, Craqui supone que
alguien lo robó: **desconecta la conexión entera**. Todos los tokens de ese negocio dejan de funcionar, la
respuesta es `invalid_grant`, y el propietario ve en sus aplicaciones conectadas que se desconectó por seguridad.
Para volver, el propietario tiene que conectar tu app otra vez desde tu web.

Lo mismo pasa si un código de autorización se presenta dos veces.

## Qué hacer ante `invalid_grant`

`/oauth/token` responde `400` con `error: "invalid_grant"` cuando el refresh token (o el código) no sirve: venció,
se revocó, la conexión se desconectó, se reutilizó, o es de otra app. La respuesta no dice cuál de todas.

* **No reintentes** con el mismo refresh token: no va a funcionar.
* Marca la conexión con ese negocio como desconectada en tu sistema.
* Muéstrale al propietario un botón para **volver a conectar** tu app, que lo lleve por el flujo de
  [Conectar un negocio](https://docs.craqui.com/apps/authorization-code.md).

Si la respuesta es `401` con `error: "invalid_client"`, el problema no es el token sino tu app: su secreto no
es válido o fue revocado, o Craqui suspendió tu app. Lee [Errores y límites](https://docs.craqui.com/apps/errors.md).

## Los permisos de un token renovado

Un token renovado tiene los permisos de la conexión, nunca más de los que tenía el anterior. Lee `scope` en
cada respuesta: si cambió, actualiza lo que tu app ofrece en ese negocio. Lee [Permisos](https://docs.craqui.com/apps/permissions.md).

## Desconecta un negocio

Cuando un negocio da de baja tu servicio, o ya no lo necesitas, revoca su conexión. Revocar el
**refresh token** desconecta el negocio entero: sus tokens dejan de funcionar y el propietario la ve como
desconectada en sus aplicaciones conectadas.

```bash theme={null}
curl -s -o /dev/null -w "%{http_code}\n" https://api.craqui.com/oauth/revoke \
  -u "$CRAQUI_CLIENT_ID:$CRAQUI_CLIENT_SECRET" \
  -d token="$CRAQUI_REFRESH_TOKEN"
```

* La respuesta es `200` sin cuerpo, también si el token ya estaba revocado o no es de tu app (RFC 7009).
* Revocar un **access token** corta solo ese token; la conexión y su refresh siguen.
* Después de revocar, borra los tokens de ese negocio de tu base de datos.

Con `oauth4webapi`:

```ts theme={null}
const { refreshToken } = await loadConnection(businessId);
const response = await oauth.revocationRequest(as, client, clientAuth, refreshToken);
await oauth.processRevocationResponse(response);
```

## Cuando el negocio desconecta tu app

El propietario puede desconectar tu app en cualquier momento desde su panel. Desde ese momento, en menos de un
minuto:

* la API responde `401` [`app_connection_revoked`](https://docs.craqui.com/errors/app_connection_revoked) a los access tokens de ese
  negocio;
* `/oauth/token` responde `invalid_grant` al refresh token.

No hay aviso a tu servidor: lo sabes por esas respuestas. Trátalo como un `invalid_grant`: marca la conexión
como desconectada y ofrece volver a conectar.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.