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

# Introspect token

> Check whether a Fire spark storefront access token is still valid so a partner proxy can reuse it on its own endpoints.

Use this endpoint when a partner runs its own API next to Storefront and wants to accept the **same** customer access token Fire spark already issued. The partner does not mint a token and does not receive the Fire spark signing secret. It sends the token here; Fire spark verifies the signature and expiration.

The request is public, like [token exchange](https://firespark.cloud/docs/storefront-api/oauth/exchange/post.md). Always `200`. `active` tells you whether the token is live.

## When to use introspection

Use this endpoint when:

* A channel already exchanged an OIDC ID token for a Fire spark access token
* A partner proxy or custom API needs to protect its own routes with that same token
* The partner should only confirm the token is real and still within its one-hour lifetime

> [!TIP]
>
> Do not verify the JWT locally. Storefront access tokens are signed with a
> private Fire spark secret. Introspection is the supported way for a partner
> server to check them.

## How a partner proxy uses it

**1. The channel obtains a Fire spark access token**

The client signs the customer in with your identity provider, then calls
[`POST /oauth/exchange`](https://firespark.cloud/docs/storefront-api/oauth/exchange/post). Fire spark
returns a Bearer token that lasts one hour.

**2. The client calls the partner proxy**

The same `Authorization: Bearer` header goes to the partner endpoint. The
partner does not decode or trust the token on its own.

**3. The proxy introspects the token**

The proxy `POST`s the token to `/oauth/introspect`. Fire spark checks the
signature, `aud`, and `exp`.

**4. The proxy authorizes its own route**

When `active` is `true`, compare `merchant_id` and `env` to the partner
tenant, then handle the request. Cache the result until `exp`.

```mermaid theme={null}
sequenceDiagram
  participant Client
  participant Proxy as Partner proxy
  participant Spark as Fire spark
  Client->>Spark: POST /oauth/exchange
  Spark-->>Client: access_token
  Client->>Proxy: Authorization Bearer access_token
  Proxy->>Spark: POST /oauth/introspect
  Spark-->>Proxy: active, merchant_id, exp
  Proxy-->>Client: Partner response
```

## Introspection request

Send a `POST` to `/oauth/introspect` with `Content-Type: application/json`. No `Authorization` header is required on this route.

```javascript Partner proxy theme={null}
const token = request.headers.get("authorization")?.replace("Bearer ", "");

const response = await fetch(
  "https://firespark.cloud/api/storefront/v1/oauth/introspect",
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ token })
  }
);

const { data } = await response.json();
if (!data.active) {
  return new Response("Unauthorized", { status: 401 });
}
if (data.merchant_id !== MERCHANT_ID) {
  return new Response("Forbidden", { status: 403 });
}
```

```bash cURL theme={null}
curl -X POST "https://firespark.cloud/api/storefront/v1/oauth/introspect" \
  -H "Content-Type: application/json" \
  -d '{ "token": "FIRESPARK_ACCESS_TOKEN" }'
```

```json Active theme={null}
{
  "data": {
    "active": true,
    "sub": "customer-external-id",
    "customer_id": "customer-external-id",
    "merchant_id": "11111111-1111-4111-8111-111111111111",
    "env": "22222222-2222-4222-8222-222222222222",
    "oauth_app_id": "33333333-3333-4333-8333-333333333333",
    "token_type": "Bearer",
    "exp": 1770000000,
    "env_is_prod": 1
  }
}
```

```json Inactive theme={null}
{
  "data": {
    "active": false
  }
}
```

### Request body

| Field | Required | Description |
| - | - | - |
| `token` | Yes | The Fire spark storefront access token issued by [`POST /oauth/exchange`](https://firespark.cloud/docs/storefront-api/oauth/exchange/post). |

### Response fields

| Field | When | Description |
| - | - | - |
| `active` | Always | `true` when the signature is valid, `aud` is `storefront`, and the token has not expired. |
| `sub` | Active | Customer identifier from the original ID token `sub`. |
| `customer_id` | Active | Same value as `sub`. |
| `merchant_id` | Active | Merchant bound to the token. Compare this to the partner tenant. |
| `env` | Active | Environment bound to the token. |
| `oauth_app_id` | Active | Identity provider app that issued the original ID token. |
| `token_type` | Active | Always `Bearer`. |
| `exp` | Active | Unix expiration time in seconds. Cache the introspection result until this instant. |
| `env_is_prod` | Active, when known | `1` in production, `0` otherwise. |

> [!NOTE]
>
> Expired, forged, or malformed tokens return `{ "active": false }` with HTTP
> `200`. The body does not say why. Missing `token` is a `400` validation error.

## Caching

A live token stays valid until `exp`. There is no revocation list today, so a cache keyed by the token and held until `exp` matches a fresh introspection call.

```javascript Cache until exp theme={null}
const cached = cache.get(token);
if (cached && cached.exp > Math.floor(Date.now() / 1000)) {
  return cached;
}
const { data } = await introspect(token);
if (data.active) cache.set(token, data);
return data;
```

## Security properties

* **No partner-issued tokens:** The only access token is the one Fire spark minted at exchange.
* **No shared signing secret:** Partners never receive `AUTH_SECRET`. They cannot forge storefront tokens.
* **Merchant binding:** Always compare `merchant_id` (and `env`) before serving partner data. A valid token for another merchant must not pass.
* **Short lifetime:** Tokens expire after one hour. Re-exchange through the channel when the IdP session refreshes.

> [!WARNING]
>
> Always call this endpoint over HTTPS. Do not log access tokens. Treat an
> active response as proof of the customer and merchant claims only — authorize
> partner-specific actions on your side.


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