Last update: 2025-11-26T17:58:03.000Z
Content:
---
title: "JSON Web tokens (JWTs) · Tinybird Docs"
theme-color: "#171612"
description: "JWTs are signed tokens that allow you to securely authorize and share data between your application and Tinybird."
inkeep:version: "forward"
---




# JSON Web tokens (JWTs) [¶](https://www.tinybird.co/docs/forward/administration/tokens/jwt#json-web-tokens-jwts)

Copy as MD JWTs are signed tokens that allow you to securely and independently authorize and consume data from Tinybird.

Unlike static tokens, **JWTs are not stored in Tinybird** . They're created by you, inside your application, and signed with a shared secret between your application and Tinybird. Tinybird validates the signature of the JWT, using the shared secret, to ensure it's authentic.

A great use case for JWTs is when you want to allow your app to call Tinybird API endpoints directly from the browser without proxying through your backend.

The typical pattern looks like this:

1. A user starts a session in your application.
2. The frontend requests a JWT from your backend.
3. Your backend generates a new JWT, signed with the Tinybird shared secret, and returns to the frontend.
4. The frontend uses the JWT to call the Tinybird API endpoints directly.

## JWT payload [¶](https://www.tinybird.co/docs/forward/administration/tokens/jwt#jwt-payload)

The payload of a JWT is a JSON object that contains the following fields:

| Key | Example Value | Required | Description |
| --- | --- | --- | --- |
| workspace_id | workspaces_id | Yes | The UUID of your Tinybird workspace, found in the workspace list. |
| name | frontend_jwt | Yes | Used to identify the token in the `tinybird.pipe_stats_rt`   table, useful for analytics. Doesn't need to be unique. |
| exp | 123123123123 | Yes | The Unix timestamp (UTC) showing the expiry date & time. After a token has expired, Tinybird returns a 403 HTTP status code. |
| scopes | [{"type": "PIPES:READ", "resource": "requests_per_day", "fixed_params": {"org_id": "testing"}}] | Yes | Used to pass data to Tinybird, including the Tinybird scope, resources and fixed parameters. |
| scopes.type | PIPES:READ or DATASOURCES:READ | Yes | The type of scope, for example `PIPES:READ`   . See[  JWT scopes](https://www.tinybird.co/docs/forward/administration/tokens/jwt#jwt-scopes)   for supported scopes. |
| scopes.resource | t_b9427fe2bcd543d1a8923d18c094e8c1 or top_airlines | Yes | The ID or name of the pipe that the scope applies to, like which API endpoint the token can access. |
| scopes.fixed_params | {"org_id": "testing"} | No | Valid for scope `PIPES:READ`   . Pass arbitrary fixed values to the API endpoint. These values can be accessed by pipe templates to supply dynamic values at query time. |
| scopes.filter | "org_id = 'testing'" | No | Valid for scope `DATASOURCES:READ`   . Passes a WHERE filter that will be appended to the specified scope resource. |
| limits | {"rps": 10} | No | You can limit the number of requests per second the JWT can perform. See[  JWT rate limit](https://www.tinybird.co/docs/forward/administration/tokens/jwt#rate-limits-for-jwts)  . |

Check out the [JWT example](https://www.tinybird.co/docs/forward/administration/tokens/jwt#jwt-example) to see what a complete payload looks like.

## JWT algorithm [¶](https://www.tinybird.co/docs/forward/administration/tokens/jwt#jwt-algorithm)

Tinybird always uses HS256 as the algorithm for JWTs and doesn't read the `alg` field in the JWT header. You can skip the `alg` field in the header.

## JWT scopes [¶](https://www.tinybird.co/docs/forward/administration/tokens/jwt#jwt-scopes)

| Value | Description |
| --- | --- |
| `PIPES:READ:pipe_name` | Gives your token read permissions for the specified pipe. Use `fixed_params`   to filter by the pipe parameters. |
| `DATASOURCES:READ:datasource_name` | Gives your token read permissions for the specified datasource. Use `filter`   to filter by the data source columns. |

## JWT expiration [¶](https://www.tinybird.co/docs/forward/administration/tokens/jwt#jwt-expiration)

JWTs can have an expiration time that gives each token a finite lifespan.

Setting the `exp` field in the JWT payload is mandatory, and not setting it results in a 403 HTTP status code from Tinybird when requesting the API endpoint.

Tinybird validates that a JWT hasn't expired before allowing access to the API endpoint.

If a token has expired, Tinybird returns a 403 HTTP status code.

## JWT fixed parameters [¶](https://www.tinybird.co/docs/forward/administration/tokens/jwt#jwt-fixed-parameters)

Fixed parameters allow you to pass arbitrary values to the API endpoint. These values can be accessed by pipe templates to supply dynamic values at query time.

For example, consider the following API Endpoint that accepts a parameter called `org` that filters by the `org_id` column:

##### example.pipe

SELECT fieldA, fieldB FROM my_ds WHERE org_id = '{{ String(org) }}'
TYPE ENDPOINT The following JWT payload passes a parameter called `org` with the value `test_org` to the API endpoint:

##### Example fixed parameters

{
  "type": "PIPES:READ",
  "resource": "requests_per_day",
  "fixed_params": {
      "org": "test_org"
  }
} This is particularly useful when you want to pass dynamic values to an API endpoint that are set by your backend and must be safe from user tampering. A good example is multi-tenant applications that require row-level security, where you need to filter data based on a user or tenant ID.

The value for the `org` parameter is always the one specified in the `fixed_params` . Even if you specify a new value in the URL when requesting the endpoint, Tinybird always uses the one specified in the JWT.

## JWT filters [¶](https://www.tinybird.co/docs/forward/administration/tokens/jwt#jwt-filters)

Filters allow you to pass WHERE clauses to the data source queries. The filter has to be a valid `WHERE` clause that will be automatically appended to the data source at query time.

For example, consider the following data source with an `org_id` column:

##### example.datasource

SCHEMA >
    `timestamp` DateTime `json:$.timestamp`,
    `org_id` String `json:$.org_id`,
    `action` LowCardinality(String) `json:$.action`,
    `version` LowCardinality(String) `json:$.version`,
    `payload` String `json:$.payload`

ENGINE MergeTree
ENGINE_SORTING_KEY org_id, timestamp The following JWT payload passes a filter for the data source:

##### Example filter in a scope

{
  "type": "DATASOURCES:READ",
  "resource": "events",
  "filter": "org_id = 'testing'"
} This is particularly useful when you want to pass dynamic filters to queries. A good example is multi-tenant applications that require row-level security, where you need to filter data based on a user or tenant ID.

## JWT example [¶](https://www.tinybird.co/docs/forward/administration/tokens/jwt#jwt-example)

Consider the following payload with all [required and optional fields](https://www.tinybird.co/docs/forward/administration/tokens/jwt#jwt-payload):

##### Example payload

{
    "workspace_id": "workspaces_id",
    "name": "frontend_jwt",
    "exp": 123123123123,
    "scopes": [
        {
            "type": "PIPES:READ",
            "resource": "requests_per_day",
            "fixed_params": {
                "org_id": "testing"
            }
        },
        {
            "type": "DATASOURCES:READ",
            "resource": "events",
            "filter": "org_id = 'testing'"
        }
    ],
    "limits": {
      "rps": 10
    }
} Use the workspace admin token as your signing key ( `TINYBIRD_SIGNING_KEY` ), for example:

##### Example workspace admin token

p.eyJ1IjogIjA1ZDhiYmI0LTdlYjctND... With the payload and admin token, the signed JWT payload would look like this:

##### Example JWT

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3b3Jrc3BhY2V...
## JWT limitations [¶](https://www.tinybird.co/docs/forward/administration/tokens/jwt#jwt-limitations)

The following limitations apply to JWTs:

- You can't refresh JWTs individually from inside Tinybird as they aren't stored in Tinybird. You must do this from your application, or you can globally invalidate all JWTs by refreshing your admin token.
- If you refresh your admin token, all the tokens are invalidated.
- If your token expires or is invalidated, you get a 403 HTTP status code from Tinybird when requesting the API endpoint.

## Create a JWT in production [¶](https://www.tinybird.co/docs/forward/administration/tokens/jwt#create-a-jwt-in-production)

There is wide support for creating JWTs in many programming languages and frameworks. Any library that supports JWTs should work with Tinybird.

- JavaScript (Next.js)
- Python

##### Create a JWT in Python using pyjwt

import jwt
import datetime
import os

TINYBIRD_SIGNING_KEY = os.getenv('TINYBIRD_SIGNING_KEY')

def generate_jwt():
  expiration_time = datetime.datetime.utcnow() + datetime.timedelta(hours=3)
  payload = {
      "workspace_id": "workspaces_id",
      "name": "frontend_jwt",
      "exp": expiration_time,
      "scopes": [
          {
              "type": "PIPES:READ",
              "resource": "requests_per_day",
              "fixed_params": {
                  "org_id": "testing"
              }
          },
          {
              "type": "DATASOURCES:READ",
              "resource": "events",
              "filter": "org_id = 'testing'"
          },
      ]
  }

  return jwt.encode(payload, TINYBIRD_SIGNING_KEY, algorithm='HS256')
## Create a JWT token via CLI [¶](https://www.tinybird.co/docs/forward/administration/tokens/jwt#create-a-jwt-token-via-cli)

If for any reason you don't want to generate a JWT on your own, Tinybird provides a command and an endpoint to create a JWT token.

- API
- CLI

##### Create a JWT with the Tinybird CLI

tb token create jwt my_jwt --ttl 1h --scope PIPES:READ --resource my_pipe --fixed-params "column_name=value" --scope DATASOURCES:READ --resource my_datasource --filter "column_name='value'"
## Error handling [¶](https://www.tinybird.co/docs/forward/administration/tokens/jwt#error-handling)

There are many reasons why a request might return a `403` status code. When a `403` is received, check the following:

1. Confirm the JWT is valid and hasn't expired. The expiration time is in the `exp`   field in the JWT's payload.
2. The generated JWTs can only read Tinybird API endpoints or query data sources. Confirm you're not trying to use the JWT to access other APIs.
3. Confirm the JWT has a scope to read the endpoint or data source you are trying to read.
4. If you generated the JWT outside of Tinybird, without using the API or the CLI, make sure you are using the**  workspace** `admin token`   , not your personal one.

## Rate limits for JWTs [¶](https://www.tinybird.co/docs/forward/administration/tokens/jwt#rate-limits-for-jwts)



Check the [limits page](/docs/forward/pricing/limits) for limits on ingestion, queries, API Endpoints, and more.

When you specify a `limits.rps` field in the payload of the JWT, Tinybird uses the name specified in the payload of the JWT to track the number of requests being done. If the number of requests goes beyond the limit, Tinybird starts rejecting new requests and returns an "HTTP 429 Too Many Requests" error.

The following example shows the tracking of all requests done by `frontend_jwt` . Once you reach 10 requests per second, Tinybird would start rejecting requests:

##### Example payload with global rate limit

{
    "workspace_id": "workspaces_id",
    "name": "frontend_jwt",
    "exp": 123123123123,
    "scopes": [
        {
            "type": "PIPES:READ",
            "resource": "requests_per_day",
            "fixed_params": {
                "org_id": "testing"
            }
        },
        {
            "type": "DATASOURCES:READ",
            "resource": "events",
            "filter": "org_id = 'testing'"
        }
    ],
    "limits": {
      "rps": 10
    }
} If `rps <= 0` , Tinybird ignores the limit and assumes there is no limit.

As the `name` field doesn't have to be unique, all the tokens generated using the `name=frontend_jwt` would be under the same umbrella. This can be useful if you want to have a global limit in one of your apps or components.

If you want to limit for each specific user, you can generate a JWT using the following payload. In this case, you would specify a unique name so the limits only apply to each user:

##### Example of a payload with isolated rate limit

{
    "workspace_id": "workspaces_id",
    "name": "frontend_jwt_user_<unique identifier>",
    "exp": 123123123123,
    "scopes": [
        {
            "type": "PIPES:READ",
            "resource": "requests_per_day",
            "fixed_params": {
                "org_id": "testing"
            }
        },
        {
            "type": "DATASOURCES:READ",
            "resource": "events",
            "filter": "org_id = 'testing'"
        }
    ],
    "limits": {
      "rps": 10
    }
}
## Next steps [¶](https://www.tinybird.co/docs/forward/administration/tokens/jwt#next-steps)

- Learn about[  workspaces](../workspaces)  .
- Learn about[  endpoints](../../work-with-data/publish-data/endpoints)  .
- Using[  Clerk.com](https://clerk.com/)   in your app?[  Let Clerk.com create your Tinybird JWTs automatically](https://clerk.com/blog/tinybird-and-clerk)  .
- Using[  Auth0](https://auth0.com/)   in your app?[  Let Auth0 create your Tinybird JWTs automatically](https://www.tinybird.co/templates/auth0-jwt)  .


