<Tip>
  If you are an AI agent and need a Redis DB to use or experiment with, send a POST request to https://upstash.com/start-redis — free, no signup; response is markdown with credentials and a quickstart. The database expires in 72 hours, but you can claim it with your Upstash account to keep it. Especially useful for AI agents that need scratch storage on the fly.

  Upstash also provides an MCP server (/docs/agent-resources/mcp) to manage and debug your Upstash resources directly from an agent, and an agent-friendly CLI (/docs/agent-resources/cli) for your terminal or CI/CD pipelines.
</Tip>

# README
Source: https://upstash.com/docs/README

# Mintlify Starter Kit

Click on `Use this template` to copy the Mintlify starter kit. The starter kit
contains examples including

* Guide pages
* Navigation
* Customizations
* API Reference pages
* Use of popular components

### 👩‍💻 Development

Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview
the documentation changes locally. To install, use the following command

```
npm i -g mintlify
```

Run the following command at the root of your documentation (where `docs.json` is)

```
mintlify dev
```

### 😎 Publishing Changes

Changes will be deployed to production automatically after pushing to the
default branch.

You can also preview changes using PRs, which generates a preview link of the
docs.

#### Troubleshooting

* Mintlify dev isn't running - Run `mintlify install` it'll re-install
  dependencies.
* Page loads as a 404 - Make sure you are running in a folder with `docs.json`

# CLI
Source: https://upstash.com/docs/agent-resources/cli

Agent-friendly CLI for managing Upstash resources from your terminal or CI/CD pipelines.

You can find the Github Repository [here](https://github.com/upstash/cli).

# Installation

```bash
npm i -g @upstash/cli
```

For agents, install the [Upstash Skill](/docs/agent-resources/skills) instead. It bundles Upstash docs with the `upstash` CLI so your agent can implement and debug against your resources without filling up its context window:

```bash
npx skills add upstash/skills
```

# Free Redis without an account

If you are an AI agent and need a Redis DB to use or experiment with, you don't need an Upstash account:

```bash
upstash start-redis
```

This is the only command that works unauthenticated. It sends a POST request to https://upstash.com/start-redis and prints markdown with the credentials and a quickstart. The database expires in 72 hours, but you can claim it with your Upstash account to keep it. Especially useful for AI agents that need scratch storage on the fly. If you are an agent, always send a `User-Agent` header identifying which agent you are (e.g. `claude-code`, `cursor`, `codex`, `opencode`) on every request you make to this API.

Pass `--id` to re-fetch the credentials of a database you created earlier:

```bash
upstash start-redis --id $DB_ID
```

# Authentication

The CLI needs your account email and a developer API key. Grab one from the [Upstash Console under Account → API Keys](https://console.upstash.com/account/api), then set credentials using whichever method fits your workflow.

## 1. Saved login (recommended)

Saves your credentials to `~/.config/upstash/config.json` (taken from `$XDG_CONFIG_HOME`) so you only have to do it once per machine.

```bash
upstash login
```

## 2. Environment variables

The CLI reads `UPSTASH_EMAIL` and `UPSTASH_API_KEY` from the process environment, and also auto-loads them from a `.env` file in the current directory.

```bash
# Exported in your shell or CI
export UPSTASH_EMAIL=you@example.com
export UPSTASH_API_KEY=your_api_key
```

```bash
# Or defined in the .env file of your current working directory (loaded automatically)
UPSTASH_EMAIL=you@example.com
UPSTASH_API_KEY=your_api_key
```

Use `--env-path` to point at a different file:

```bash
upstash --env-path .dev.vars redis list
```

## 3. Per-command flags

Override whatever is set above for a single invocation. Handy for scripts that switch between accounts.

```bash
upstash --email you@example.com --api-key your_api_key redis list
```

<Note>
  When credentials come from multiple sources, precedence is:

  `flags` > `environment variables` > `.env` > `saved config file`
</Note>

# Usage

```bash
upstash start-redis  # free temporary Redis database, no account needed
upstash redis   # Redis databases
upstash team    # Teams and members
upstash vector  # Vector indexes
upstash search  # Search indexes
upstash qstash  # QStash instances
```

Use `--help` on any command or subcommand for details:

```bash
upstash --help
upstash redis --help
upstash redis create --help
```

## Output

All successful output is JSON, except `start-redis`, which prints markdown. Pipe JSON output to `jq` for filtering:

```bash
upstash redis list | jq '.[].database_id'
upstash vector list | jq '.[] | {id, name, region}'
upstash qstash list | jq '.[] | {id, region}'
upstash team members --team-id $TEAM_ID | jq '.[].member_email'
```

Use `--dry-run` on destructive commands (`delete`, `remove-member`) to preview the action before executing it.

# Redis

## Core

```bash
upstash redis list
upstash redis get --db-id $DB_ID
upstash redis get --db-id $DB_ID --hide-credentials
upstash redis create --name $NAME --region $REGION
upstash redis create --name $NAME --region $REGION --read-regions $REGION_1 $REGION_2
upstash redis delete --db-id $DB_ID --dry-run
upstash redis delete --db-id $DB_ID
upstash redis rename --db-id $DB_ID --name $NEW_NAME
upstash redis reset-password --db-id $DB_ID
upstash redis stats --db-id $DB_ID
```

## Configuration

```bash
upstash redis enable-tls --db-id $DB_ID
upstash redis enable-eviction --db-id $DB_ID
upstash redis disable-eviction --db-id $DB_ID
upstash redis enable-autoupgrade --db-id $DB_ID
upstash redis disable-autoupgrade --db-id $DB_ID
upstash redis change-plan --db-id $DB_ID --plan $PLAN        # free, payg, pro, paid
upstash redis update-budget --db-id $DB_ID --budget $BUDGET_CENTS
upstash redis update-regions --db-id $DB_ID --read-regions $REGION_1 $REGION_2
upstash redis move-to-team --db-id $DB_ID --team-id $TEAM_ID
```

## Backups

```bash
upstash redis backup list --db-id $DB_ID
upstash redis backup create --db-id $DB_ID --name $NAME
upstash redis backup delete --db-id $DB_ID --backup-id $BACKUP_ID --dry-run
upstash redis backup delete --db-id $DB_ID --backup-id $BACKUP_ID
upstash redis backup restore --db-id $DB_ID --backup-id $BACKUP_ID
upstash redis backup enable-daily --db-id $DB_ID
upstash redis backup disable-daily --db-id $DB_ID
```

## Execute Redis commands directly

`redis exec` runs commands straight against the Redis REST API. It uses the database token, not your Developer API key. Get `endpoint` and `rest_token` from `upstash redis get --db-id $DB_ID`.

```bash
upstash redis exec --db-url $REDIS_URL --db-token $REDIS_TOKEN SET key value
upstash redis exec --db-url $REDIS_URL --db-token $REDIS_TOKEN GET key
upstash redis exec --db-url $REDIS_URL --db-token $REDIS_TOKEN --json '["SET","key","value"]'
```

`--db-url` and `--db-token` can be omitted if `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` are set via environment variable or `.env` file.

# Team

```bash
upstash team list
upstash team create --name $NAME
upstash team create --name $NAME --copy-cc
upstash team delete --team-id $TEAM_ID --dry-run
upstash team delete --team-id $TEAM_ID
upstash team members --team-id $TEAM_ID
upstash team add-member --team-id $TEAM_ID --member-email $EMAIL --role $ROLE
upstash team remove-member --team-id $TEAM_ID --member-email $EMAIL --dry-run
upstash team remove-member --team-id $TEAM_ID --member-email $EMAIL
```

# Vector

```bash
upstash vector list
upstash vector get --index-id $INDEX_ID
upstash vector create --name $NAME --region $REGION --similarity-function $FUNCTION --dimension-count $DIMENSION
upstash vector delete --index-id $INDEX_ID --dry-run
upstash vector delete --index-id $INDEX_ID
upstash vector rename --index-id $INDEX_ID --name $NEW_NAME
upstash vector reset-password --index-id $INDEX_ID
upstash vector set-plan --index-id $INDEX_ID --plan $PLAN          # free, payg, fixed
upstash vector transfer --index-id $INDEX_ID --target-account $TARGET_ACCOUNT_ID
upstash vector stats
upstash vector index-stats --index-id $INDEX_ID
upstash vector index-stats --index-id $INDEX_ID --period $PERIOD   # 1h, 3h, 12h, 1d, 3d, 7d, 30d
```

# Search

```bash
upstash search list
upstash search get --index-id $INDEX_ID
upstash search create --name $NAME --region $REGION --type $INDEX_TYPE
upstash search delete --index-id $INDEX_ID --dry-run
upstash search delete --index-id $INDEX_ID
upstash search rename --index-id $INDEX_ID --name $NEW_NAME
upstash search reset-password --index-id $INDEX_ID
upstash search transfer --index-id $INDEX_ID --target-account $TARGET_ACCOUNT_ID
upstash search stats
upstash search index-stats --index-id $INDEX_ID
upstash search index-stats --index-id $INDEX_ID --period $PERIOD   # 1h, 3h, 12h, 1d, 3d, 7d, 30d
```

# QStash

```bash
upstash qstash list
upstash qstash get --qstash-id $QSTASH_ID
upstash qstash rotate-token --qstash-id $QSTASH_ID
upstash qstash set-plan --qstash-id $QSTASH_ID --plan $PLAN             # paid, qstash_fixed_1m, qstash_fixed_10m, qstash_fixed_100m
upstash qstash stats --qstash-id $QSTASH_ID
upstash qstash stats --qstash-id $QSTASH_ID --period $PERIOD            # 1h, 3h, 12h, 1d, 3d, 7d, 30d
upstash qstash ipv4
upstash qstash move-to-team --qstash-id $QSTASH_ID --target-team-id $TARGET_TEAM_ID
upstash qstash update-budget --qstash-id $QSTASH_ID --budget $BUDGET_DOLLARS   # 0 = no limit
upstash qstash enable-prodpack --qstash-id $QSTASH_ID
upstash qstash disable-prodpack --qstash-id $QSTASH_ID
```

# llms.txt
Source: https://upstash.com/docs/agent-resources/llms-txt

# MCP Server
Source: https://upstash.com/docs/agent-resources/mcp

The Upstash MCP server lets your agent manage and debug your Upstash resources directly, across Redis, QStash, Workflow, and [Upstash Box](/docs/box/overall/quickstart).

<Tip>
  Only running commands against a single Redis database? Use the dedicated [Redis MCP](/docs/redis/sdks/mcp) with only your REST/TCP credentials.
</Tip>

<Tip>
  If you are an AI agent and need a Redis DB to use or experiment with, call the `redis_database_start_free` tool — or send a POST request to https://upstash.com/start-redis directly. Free, no signup, no API key; the response is markdown with credentials and a quickstart. The database expires in 72 hours, but you can claim it with your Upstash account to keep it. Especially useful for AI agents that need scratch storage on the fly. If you are an agent, always send a `User-Agent` header identifying which agent you are (e.g. `claude-code`, `cursor`, `codex`, `opencode`) on every request you make to this API.
</Tip>

Find the GitHub repository [here](https://github.com/upstash/mcp-server).

<Note>
  For most workflows, prefer installing the [Upstash Skill](/docs/agent-resources/skills) and letting your agent drive [`@upstash/cli`](/docs/agent-resources/cli) over running the MCP server.
</Note>

# Example prompts

## Redis

* "Start a free temporary Redis I can experiment with"
* "Create a new Redis database in us-east-1"
* "List my databases sorted by memory usage"
* "Give me the schema of how users are stored in this Redis"
* "Find all session keys expiring in the next hour and show me their payloads"
* "Create a backup of this db, then clear it"
* "Show me throughput spikes during the last 7 days"

## QStash & Workflow

* "Check the QStash logs and figure out why my webhook keeps failing"
* "Find failed workflow runs for user `@ysfk_0x` in the last 24 hours"
* "Retry the failed workflow run that started 2 hours ago"
* "Summarize what's in the DLQ right now, grouped by error type"
* "Pause the `daily-report` schedule until Monday"

## Upstash Box

* "Spin up a Box, clone this repo, and run the tests"
* "Snapshot this Box and create 5 copies from it, assign each one a GitHub issue"
* "My Box keeps failing to start, check the logs and tell me what's wrong"

# Credentials

Before installing, grab your credentials:

* **Email**: your Upstash account email
* **API Key**: create one at [Upstash Console → Account → API Keys](https://console.upstash.com/account/api)

<Note>
  Readonly API keys are supported. When the server starts with one, it automatically disables every tool that would modify state, such as creating databases, deleting backups, or retrying workflows. Your agent can still read and query your account, but it cannot make changes.
</Note>

These are passed to the server on startup. The base command every client uses is:

```bash
npx -y @upstash/mcp-server@latest --email YOUR_EMAIL --api-key YOUR_API_KEY
```

# MCP Clients

The Upstash MCP server works with any MCP-compatible client. If your client isn't listed, check its documentation for how to add a stdio MCP server, then point it at the command above.

<AccordionGroup>
  <Accordion title="Claude Code">
    Run this command in your terminal. See the [Claude Code MCP docs](https://docs.anthropic.com/en/docs/claude-code/mcp) for more info.

    ```sh
    claude mcp add --transport stdio upstash -- npx -y @upstash/mcp-server@latest --email YOUR_EMAIL --api-key YOUR_API_KEY
    ```
  </Accordion>

  <Accordion title="Cursor">
    Go to `Settings` → `Cursor Settings` → `MCP` → `Add new global MCP server`, or edit `~/.cursor/mcp.json` directly. You can also scope it to a project by creating `.cursor/mcp.json` in your repo.

    Since Cursor 1.0, the one-click install button also works:

    [![Install MCP Server]()](https://cursor.com/en/install-mcp?name=upstash&config=eyJjb21tYW5kIjoibnB4IC15IEB1cHN0YXNoL21jcC1zZXJ2ZXJAbGF0ZXN0IC0tZW1haWwgWU9VUl9FTUFJTCAtLWFwaS1rZXkgWU9VUl9BUElfS0VZIn0%3D)

    ```json
    {
      "mcpServers": {
        "upstash": {
          "command": "npx",
          "args": ["-y", "@upstash/mcp-server@latest", "--email", "YOUR_EMAIL", "--api-key", "YOUR_API_KEY"]
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="Windsurf">
    Add this to your Windsurf MCP config file at `~/.codeium/windsurf/mcp_config.json`. See the [Windsurf MCP docs](https://docs.windsurf.com/windsurf/cascade/mcp) for more info.

    ```json
    {
      "mcpServers": {
        "upstash": {
          "command": "npx",
          "args": ["-y", "@upstash/mcp-server@latest", "--email", "YOUR_EMAIL", "--api-key", "YOUR_API_KEY"]
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="OpenCode">
    Add this to your OpenCode configuration file (`~/.config/opencode/opencode.json` or a project-level `opencode.json`). See the [OpenCode MCP docs](https://opencode.ai/docs/mcp-servers) for more info.

    ```json
    {
      "mcp": {
        "upstash": {
          "type": "local",
          "command": ["npx", "-y", "@upstash/mcp-server@latest", "--email", "YOUR_EMAIL", "--api-key", "YOUR_API_KEY"],
          "enabled": true
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="OpenAI Codex">
    See the [OpenAI Codex MCP docs](https://developers.openai.com/codex/mcp) for more info.

    #### Using the CLI

    ```sh
    codex mcp add upstash -- npx -y @upstash/mcp-server@latest --email YOUR_EMAIL --api-key YOUR_API_KEY
    ```

    #### Manual configuration

    Add this to your Codex config file (`~/.codex/config.toml` or `.codex/config.toml`):

    ```toml
    [mcp_servers.upstash]
    command = "npx"
    args = ["-y", "@upstash/mcp-server@latest", "--email", "YOUR_EMAIL", "--api-key", "YOUR_API_KEY"]
    startup_timeout_sec = 20
    ```

    <Note>
      If you see startup timeout errors, increase `startup_timeout_sec` to `40`.
    </Note>
  </Accordion>

  <Accordion title="VS Code">
    Add this to your VS Code MCP config file at `.vscode/mcp.json`, or paste it into the `mcp.servers` user setting. See the [VS Code MCP docs](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) for more info.

    [<img alt="Install in VS Code (npx)" />](https://insiders.vscode.dev/redirect?url=vscode%3Amcp%2Finstall%3F%7B%22name%22%3A%22upstash-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40upstash%2Fmcp-server%40latest%22%5D%7D)

    ```json
    {
      "servers": {
        "upstash": {
          "type": "stdio",
          "command": "npx",
          "args": ["-y", "@upstash/mcp-server@latest", "--email", "YOUR_EMAIL", "--api-key", "YOUR_API_KEY"]
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="Google Antigravity">
    Add this to your Antigravity MCP config. See the [Antigravity MCP docs](https://antigravity.google/docs/mcp) for more info.

    ```json
    {
      "mcpServers": {
        "upstash": {
          "command": "npx",
          "args": ["-y", "@upstash/mcp-server@latest", "--email", "YOUR_EMAIL", "--api-key", "YOUR_API_KEY"]
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="Claude Desktop">
    Open Claude Desktop's developer settings and edit `claude_desktop_config.json`. See the [Claude Desktop MCP docs](https://modelcontextprotocol.io/quickstart/user) for more info.

    ```json
    {
      "mcpServers": {
        "upstash": {
          "command": "npx",
          "args": ["-y", "@upstash/mcp-server@latest", "--email", "YOUR_EMAIL", "--api-key", "YOUR_API_KEY"]
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="Gemini CLI">
    Open the Gemini CLI settings file at `~/.gemini/settings.json` and add Upstash to `mcpServers`. See [Gemini CLI Configuration](https://google-gemini.github.io/gemini-cli/docs/tools/mcp-server.html) for details.

    ```json
    {
      "mcpServers": {
        "upstash": {
          "command": "npx",
          "args": ["-y", "@upstash/mcp-server@latest", "--email", "YOUR_EMAIL", "--api-key", "YOUR_API_KEY"]
        }
      }
    }
    ```
  </Accordion>
</AccordionGroup>

# Upstash Box API key (optional)

For the MCP to interact with [Upstash Box](/docs/box/overall/quickstart), the agent needs your Box API key. By default you have to paste it into the chat (or keep it in a `.env`) every time the agent runs a Box tool. To avoid this, you can wire the key into the MCP setup itself so the server picks it up automatically on startup.

You can pass it in two ways.

#### CLI flag

```json
{
  "mcpServers": {
    "upstash": {
      "command": "npx",
      "args": [
        "-y", "@upstash/mcp-server@latest",
        "--email", "YOUR_EMAIL",
        "--api-key", "YOUR_API_KEY",
        "--box-api-key", "YOUR_BOX_API_KEY"
      ]
    }
  }
}
```

#### Environment variable

```json
{
  "mcpServers": {
    "upstash": {
      "command": "npx",
      "args": ["-y", "@upstash/mcp-server@latest", "--email", "YOUR_EMAIL", "--api-key", "YOUR_API_KEY"],
      "env": {
        "UPSTASH_BOX_API_KEY": "YOUR_BOX_API_KEY"
      }
    }
  }
}
```

# Telemetry

The server sends anonymous diagnostic info to Upstash with each request: the MCP server SDK version, your runtime version (Node, Bun, etc.), and basic platform info (OS and architecture). No account data, tool arguments, or results are collected. To opt out, add `--disable-telemetry` to the args.

# Skills
Source: https://upstash.com/docs/agent-resources/skills

Upstash Skills are packaged instructions and resources that extend your agent's capabilities across every Upstash SDK: Redis, QStash, Workflow, Vector, Search, Ratelimit, and [Upstash Box](/docs/box/overall/quickstart).

Find the GitHub repository [here](https://github.com/upstash/skills).

<Note>
  The `upstash` skill also includes the [`@upstash/cli`](/docs/agent-resources/cli) reference, so one install is all your agent needs.
</Note>

# Available skills

| Skill | Description |
|-------|-------------|
| [upstash](https://github.com/upstash/skills/tree/main/skills/upstash) | Combined skill covering all Upstash SDKs and the `upstash-cli`. |
| [upstash-box-js](https://github.com/upstash/skills/tree/main/skills/upstash-box-js) | Sandboxed cloud containers with AI agents, shell, filesystem, and git. |
| [upstash-qstash-js](https://github.com/upstash/skills/tree/main/skills/upstash-qstash-js) | Serverless messaging and scheduling via HTTP endpoints. |
| [upstash-ratelimit-js](https://github.com/upstash/skills/tree/main/skills/upstash-ratelimit-js) | Rate limiting with the Redis Rate Limit TypeScript SDK. |
| [upstash-redis-js](https://github.com/upstash/skills/tree/main/skills/upstash-redis-js) | Serverless Redis for caching, sessions, leaderboards, full-text search. |
| [upstash-search-js](https://github.com/upstash/skills/tree/main/skills/upstash-search-js) | Full-text search quick starts, core concepts, and TypeScript SDK. |
| [upstash-vector-js](https://github.com/upstash/skills/tree/main/skills/upstash-vector-js) | Vector database features, SDK usage, and framework integrations. |
| [upstash-workflow-js](https://github.com/upstash/skills/tree/main/skills/upstash-workflow-js) | Durable workflows. Define, trigger, and manage multi-step processes. |

Install the combined `upstash` skill unless you only need a single SDK.

<Note>
  The combined `upstash` skill does not bloat your agent's context. Its top-level `SKILL.md` only contains references to the underlying per-SDK skill files, so the agent loads just the sections relevant to the task at hand.
</Note>

# Example prompts

* "Set up Upstash Redis in my Next.js app for session caching"
* "Create a QStash schedule that calls `/api/daily-report` every morning at 9am UTC"
* "Add rate limiting to my `/api/login` route, 5 attempts per minute per IP"
* "Build a workflow that fans out to 10 parallel steps and waits for all of them"
* "Index these product docs into Upstash Vector and wire up semantic search"
* "Create a new Redis database named `prod-cache` in `us-east-1` using the CLI"
* "List all my Upstash databases and print their monthly costs"

# Installation

Use the [Agent Skills CLI](https://agentskills.io/) to install into any compatible agent:

```bash
npx skills add upstash/skills
```

To install only a specific SDK skill, append its name:

```bash
npx skills add upstash/skills/upstash-qstash-js
```

# Managing resources

The combined `upstash` skill includes the full `@upstash/cli` command reference, so your agent can create databases, publish QStash messages, query Vector indexes, and more straight from the shell. See the [CLI page](/docs/agent-resources/cli) for credentials and command details.

# Disable Production Pack
Source: https://upstash.com/docs/api-reference/qstash/disable-production-pack

/devops/developer-api/openapi.yml post /qstash/disable-prodpack/{id}
Disables the production pack for a QStash instance.

# Enable Production Pack
Source: https://upstash.com/docs/api-reference/qstash/enable-production-pack

/devops/developer-api/openapi.yml post /qstash/enable-prodpack/{id}
Enables the production pack for a QStash instance.

# Get QStash
Source: https://upstash.com/docs/api-reference/qstash/get-qstash

/devops/developer-api/openapi.yml get /qstash/user/{id}
Retrieves detailed information about the specified QStash user, including plan details, limits, and configuration

# Get QStash IPv4 Addresses
Source: https://upstash.com/docs/api-reference/qstash/get-qstash-ipv4-addresses

/devops/developer-api/openapi.yml get /qstash/ipv4
Returns the list of IPv4 addresses used by QStash for sending requests.

# Get QStash Stats
Source: https://upstash.com/docs/api-reference/qstash/get-qstash-stats

/devops/developer-api/openapi.yml get /qstash/stats/{id}
Retrieves detailed usage statistics for the QStash account including
daily requests, billing, bandwidth, and workflow metrics over time.

# List QStash Users
Source: https://upstash.com/docs/api-reference/qstash/list-qstash-users

/devops/developer-api/openapi.yml get /qstash/users
Retrieves a list of all QStash users associated with the account, including plan details, limits, and configuration.

# Move QStash to Team
Source: https://upstash.com/docs/api-reference/qstash/move-qstash-to-team

/devops/developer-api/openapi.yml post /qstash/move-to-team
Moves a QStash instance to a different team.

# Reset QStash Token
Source: https://upstash.com/docs/api-reference/qstash/reset-qstash-token

/devops/developer-api/openapi.yml post /qstash/rotate-token/{id}
Resets the authentication credentials for the QStash user account.
This invalidates the old password and token, and generates new ones.
Returns the updated user information with new credentials.

# Set QStash Plan
Source: https://upstash.com/docs/api-reference/qstash/set-qstash-plan

/devops/developer-api/openapi.yml post /qstash/set-plan/{id}
Changes the QStash account to a different plan type.
This operation changes the plan and associated limits for the QStash account.

# Update QStash Budget
Source: https://upstash.com/docs/api-reference/qstash/update-qstash-budget

/devops/developer-api/openapi.yml patch /qstash/update-budget/{id}
Updates the monthly spending budget limit for a QStash instance.

# Create Search Index
Source: https://upstash.com/docs/api-reference/search/create-search-index

/devops/developer-api/openapi.yml post /search
Creates a new search index with the specified configuration

# Delete Search Index
Source: https://upstash.com/docs/api-reference/search/delete-search-index

/devops/developer-api/openapi.yml delete /search/{id}
Permanently deletes a search index and all its data

# Get Index Stats
Source: https://upstash.com/docs/api-reference/search/get-index-stats

/devops/developer-api/openapi.yml get /search/{id}/stats
Retrieves statistics and metrics for a specific search index

# Get Search Index
Source: https://upstash.com/docs/api-reference/search/get-search-index

/devops/developer-api/openapi.yml get /search/{id}
Retrieves detailed information about a specific search index

# Get Search Stats
Source: https://upstash.com/docs/api-reference/search/get-search-stats

/devops/developer-api/openapi.yml get /search/stats
Get search statistics for all the search indices associated with the authenticated user

# List Search Indexes
Source: https://upstash.com/docs/api-reference/search/list-search-indexes

/devops/developer-api/openapi.yml get /search
Returns a list of all search indices belonging to the authenticated user.

# Rename Search Index
Source: https://upstash.com/docs/api-reference/search/rename-search-index

/devops/developer-api/openapi.yml post /search/{id}/rename
Renames a search index.

# Reset Password
Source: https://upstash.com/docs/api-reference/search/reset-password

/devops/developer-api/openapi.yml post /search/{id}/reset-password
This endpoint resets the regular and readonly tokens of a search index.

# Transfer Search Index
Source: https://upstash.com/docs/api-reference/search/transfer-search-index

/devops/developer-api/openapi.yml post /search/{id}/transfer
Transfers ownership of a search index to another team.
Transferring to a personal account is not supported.
However, transferring from a personal account to a team is allowed.

# Get Index Stats
Source: https://upstash.com/docs/api-reference/vector/get-index-stats

/devops/developer-api/openapi.yml get /vector/index/{id}/stats
Retrieves statistics and metrics for a specific vector index

# Get Vector Stats
Source: https://upstash.com/docs/api-reference/vector/get-vector-stats

/devops/developer-api/openapi.yml get /vector/index/stats
Get vector statistics for all the vector indices associated with the authenticated user

# Code Interpreter with Vercel AI SDK
Source: https://upstash.com/docs/box/guides/ai-sdk-code-interpreter

In this guide we'll add a **code interpreter** tool to a Vercel AI SDK chat app. When a user asks a question that needs computation — math, data analysis, statistics — the model writes code and sends it to a fresh `EphemeralBox` to run. The sandbox is isolated, disposable, and auto-expires when the session ends.

***

## 1. Installation

```bash
npm install @upstash/box @ai-sdk/anthropic @ai-sdk/react ai zod
```

Get a Box API key from the [Upstash Console](https://console.upstash.com/box) and add your environment variables:

```bash title=".env.local"
UPSTASH_BOX_API_KEY=box_xxxxxxxxxxxxxxxxxxxxxxxx
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxx
```

***

## 2. Create the API route

Each time the model decides to run code, the tool spins up a fresh `EphemeralBox`, executes the snippet, and deletes the box immediately after. Nothing persists between tool calls.

```typescript title="app/api/chat/route.ts"
import { streamText, tool, convertToModelMessages, stepCountIs } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { EphemeralBox } from "@upstash/box";
import { z } from "zod";

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: anthropic("claude-sonnet-4-6"),
    system:
      "You are a helpful assistant with access to a secure code sandbox. " +
      "When the user asks for computation, data analysis, or math — write and run code " +
      "instead of estimating. Prefer Python for numerical work, JavaScript for JSON or string processing.",
    messages: await convertToModelMessages(messages),
    stopWhen: stepCountIs(10),
    tools: {
      executeSandboxCode: tool({
        description:
          "Run Python or JavaScript code in a secure, isolated sandbox. " +
          "Use this for any math, data processing, or computation.",
        inputSchema: z.object({
          lang: z.enum(["python", "js"]).describe("Language to run"),
          code: z.string().describe("The code to execute"),
        }),
        execute: async ({ lang, code }) => {
          const box = await EphemeralBox.create({
            apiKey: process.env.UPSTASH_BOX_API_KEY,
            runtime: lang === "python" ? "python" : "node",
            ttl: 120,
          });

          try {
            const run = await box.exec.code({ lang, code, timeout: 10_000 });
            return {
              success: run.exitCode === 0,
              output: run.result,
            };
          } finally {
            await box.delete();
          }
        },
      }),
    },
  });

  return result.toUIMessageStreamResponse();
}
```

<Note>
`ttl: 120` means the box auto-deletes after 2 minutes even if the `finally` block is skipped. For longer-running scripts, increase this value.
</Note>

***

## 3. Add a simple UI

Wire up a simple chat UI with `useChat` from the AI SDK. This UI also will display tool calls so that we can test the functionality.

```typescript title="app/page.tsx"
"use client";

import { useState } from "react";
import { useChat } from "@ai-sdk/react";

export default function Page() {
  const { messages, sendMessage, status } = useChat();
  const [input, setInput] = useState("");

  function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    if (!input.trim()) return;
    sendMessage({ text: input });
    setInput("");
  }

  return (
    <div className="mx-auto flex h-screen max-w-2xl flex-col p-4">
      <h1 className="mb-4 text-lg font-semibold">Code Interpreter</h1>

      <div className="flex-1 space-y-4 overflow-y-auto">
        {messages.map((message) => (
          <div key={message.id}>
            <div className="text-xs font-medium text-gray-500">
              {message.role === "user" ? "You" : "Assistant"}
            </div>
            {message.parts.map((part, i) => {
              if (part.type === "text") {
                return (
                  <p key={i} className="whitespace-pre-wrap text-sm">
                    {part.text}
                  </p>
                );
              }
              if (part.type.startsWith("tool-")) {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                const p = part as any;
                const toolName = part.type.slice(5);
                const isDone = p.state === "output-available";
                return (
                  <div
                    key={i}
                    className="my-1 rounded border border-gray-200 bg-gray-50 p-2 text-xs"
                  >
                    <code>{toolName}</code>{" "}
                    <span className={isDone ? "text-green-600" : "text-gray-400"}>
                      {isDone ? "✓" : "running…"}
                    </span>
                    {isDone && p.output && (
                      <pre className="mt-1 overflow-x-auto">
                        {String(p.output.output)}
                      </pre>
                    )}
                  </div>
                );
              }
              return null;
            })}
          </div>
        ))}
      </div>

      <form onSubmit={handleSubmit} className="mt-4 flex gap-2">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Ask me to compute something..."
          disabled={status === "streaming"}
          className="flex-1 rounded border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-gray-400"
        />
        <button
          type="submit"
          disabled={status === "streaming"}
          className="rounded bg-black px-4 py-2 text-sm text-white disabled:opacity-40"
        >
          Send
        </button>
      </form>
    </div>
  );
}
```

***

## 4. Try it

Start your Next.js app and ask anything that needs real computation:

> *"What is the square root of 144 plus 25 factorial?"*

The model writes a Python snippet, the `executeSandboxCode` tool fires, a fresh `EphemeralBox` boots, the code runs, and the result streams back — all within a single response turn.

```
executeSandboxCode  ✓

Square root of 144: 12.0
25 factorial: 15511210043330985984000000
Sum: 1.5511210043330986e+25
```

Every tool call gets its own isolated box, so a crash in one never affects the others. The `timeout: 10_000` on `exec.code` cuts off the HTTP call after 10 seconds — without it, an infinite loop would hang until the backend times out or the `ttl` deletes the box. Raise the timeout for long-running scripts, but always set one.

# Build a Code Review Agent
Source: https://upstash.com/docs/box/guides/code-review-agent

In this guide, we're building a code review agent (like CodeRabbit or Greptile) with Upstash Box. We clone a repo, inspect the PR diff, and return structured findings with severity and suggested fixes.

***

## 1. Installation

```bash
npm install @upstash/box zod
```

Set your environment variables:

```bash title=".env"
UPSTASH_BOX_API_KEY=box_xxxxxxxxxxxxxxxxxxxxxxxx
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxx
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
```

***

## 2. Create the reviewer

```typescript title="scripts/review-pr.ts"
import { Agent, Box } from "@upstash/box"
import { writeFile } from "node:fs/promises"
import { z } from "zod"

const responseSchema = z.object({
  verdict: z.enum(["approved", "changes_requested"]),
  summary: z.string(),
  findings: z.array(
    z.object({
      severity: z.enum(["high", "medium", "low"]),
      file: z.string(),
      line: z.number().nullable(),
      issue: z.string(),
      suggestion: z.string(),
    }),
  ),
})

type ReviewResult = z.infer<typeof responseSchema>

const getRepoDir = (repo: string) =>
  repo
    .split("/")
    .at(-1)!
    .replace(/\.git$/, "")

type PullRequestInput = {
  repo: string
  base: string
  head: string
}

export async function reviewPullRequest(input: PullRequestInput): Promise<ReviewResult> {
  const box = await Box.create({
    runtime: "node",
    agent: {
      harness: Agent.ClaudeCode,
      model: "anthropic/claude-opus-4-5",
      apiKey: process.env.ANTHROPIC_API_KEY,
    },
    git: { token: process.env.GITHUB_TOKEN },
  })

  try {
    await box.git.clone({ repo: input.repo })

    const repoDir = getRepoDir(input.repo)

    const reviewRun = await box.agent.run({
      responseSchema,
      prompt: `
Repository path: /work/${repoDir}
Base branch: ${input.base}
Head branch: ${input.head}

Fetch both branches from origin, check out the head branch, and review only the code
changed in origin/${input.base}...HEAD.

Focus on:
- correctness bugs
- security issues
- performance regressions
- missing edge-case tests

Rules:
- Ignore style-only feedback.
- Report only issues caused by changed code.
- Keep each finding concrete and actionable.
- Set verdict to "changes_requested" if there is at least one high severity issue.
- If there are no meaningful issues, return verdict "approved" with empty findings.
      `.trim(),
    })

    return reviewRun.result
  } finally {
    await box.delete()
  }
}

const result = await reviewPullRequest({
  repo: "github.com/your-org/your-repo",
  base: "main",
  head: "feature/my-change",
})

await writeFile("./review-result.json", JSON.stringify(result, null, 2))
console.log(`Verdict: ${result.verdict}`)
console.log(result.summary)

for (const finding of result.findings) {
  const line = finding.line === null ? "-" : String(finding.line)
  console.log(`[${finding.severity}] ${finding.file}:${line}`)
  console.log(`Issue: ${finding.issue}`)
  console.log(`Suggestion: ${finding.suggestion}`)
}
```

***

## 3. Run the reviewer

```bash
npx tsx scripts/review-pr.ts
```

***

## 4. Use in CI

Save the JSON result from your reviewer script, then fail the CI job when changes are required.

```typescript title="scripts/check-review-result.ts"
import { readFileSync } from "node:fs"

const result = JSON.parse(readFileSync("./review-result.json", "utf8")) as {
  verdict: "approved" | "changes_requested"
}

if (result.verdict === "changes_requested") {
  process.exit(1)
}
```

This gives you an automated gate similar to CodeRabbit or Greptile, running inside an isolated, durable Box.

# Running Tests with Crabbox
Source: https://upstash.com/docs/box/guides/crabbox-setup

[Crabbox](https://crabbox.sh) is a CLI tool that runs commands inside a remote box from your local machine. This guide shows how to use it with Upstash Box to run your test suite in a clean cloud environment without touching your local setup.

***

## 1. Install Crabbox

```bash
brew install openclaw/tap/crabbox
```

***

## 2. Get Your API Key

Go to the [Upstash Console](https://console.upstash.com) and create a Box API key. Then export it in your terminal:

```bash
export UPSTASH_BOX_API_KEY=your_api_key_here
```

***

## 3. Warm Up a Box

Create a box with the `warmup` command. This provisions the box ahead of time so it's ready when you need it.

```bash
crabbox warmup --provider upstash-box --upstash-box-runtime node --upstash-box-size small
```

You can customize the box to match your project's needs:

| Flag | Description | Values |
|---|---|---|
| `--upstash-box-runtime` | Runtime environment | `node`, `python`, `golang`, `ruby`, `rust` (or `-alpine` variants) |
| `--upstash-box-size` | Box size | `small`, `medium`, `large` |
| `--upstash-box-workdir` | Working directory inside the box | Any path (default: `/workspace/home/crabbox`) |
| `--upstash-box-keep-alive` | Keep the box up and running at all times | `true` / `false` |

***

## 4. Run Your Tests

Run your test command inside the box. Use `--env-from-profile` to load environment variables from a local `.env` file — they'll be injected into the box without being stored anywhere.

```bash
crabbox run --provider upstash-box --env-from-profile .env -- pnpm test
```

You can use any package manager or test runner:

```bash
# npm
crabbox run --provider upstash-box --env-from-profile .env -- npm test

# bun
crabbox run --provider upstash-box --env-from-profile .env -- bun test
```

***

For the full list of commands and options, see the [Upstash Box provider reference](https://crabbox.sh/providers/upstash-box.html).

# Hermes Setup
Source: https://upstash.com/docs/box/guides/hermes-setup

This guide walks you through setting up [Hermes](https://github.com/NousResearch/hermes-agent) inside an Upstash Box.

***

## 1. Create a Box

Create a keep-alive box with the **Medium** size. See the [quickstart](/docs/box/overall/quickstart) if you haven't created one before.

<Note>
  Hermes is resource-intensive, so we recommend starting with a Medium box to ensure a smooth installation.
</Note>

<img />

***

## 2. Connect via SSH

Once the box is running, connect to it via SSH from your terminal. Use your [Box API key](/docs/box/overall/quickstart#1-get-your-api-key) as the password when prompted.

The `box-id` is the name of your box (e.g. `right-flamingo-14486`).

```bash
ssh <box-id>@us-east-1.box.upstash.com
```

<img />

***

## 3. Install Hermes

Run the following command to download and install Hermes inside the box.

```bash
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
```

Follow the CLI prompts to complete the setup.

Congratulations! You have successfully set up Hermes on your Upstash Box.

***

## 4. Set Init Script for Auto-Restart

To ensure Hermes restarts automatically if the box crashes, set the startup command as an init script from the Upstash Console.

<img />

The init script to use:

```bash
hermes gateway start > gateway.log 2>&1 &
```

This command runs automatically whenever the box starts, so your gateway is always available without manual intervention.

# LangChain Deep Agents
Source: https://upstash.com/docs/box/guides/langchain-deep-agents

In this guide we'll give a [LangChain Deep Agent](https://docs.langchain.com/oss/python/deepagents/overview) a real computer to work in. The [`langchain-upstash-box`](https://pypi.org/project/langchain-upstash-box/) package wraps an Upstash Box (a secure, isolated cloud container with a full Linux shell, filesystem, git, and a runtime) as a Deep Agents [sandbox backend](https://docs.langchain.com/oss/python/deepagents/backends). The agent's shell and file tools then run inside the box instead of on your machine.

***

## 1. Installation

<CodeGroup>

```bash pip
pip install langchain-upstash-box
```

```bash uv
uv add langchain-upstash-box
```

</CodeGroup>

Get a Box API key from the [Upstash Console](https://console.upstash.com/box) and export it:

```bash
export UPSTASH_BOX_API_KEY="box_xxxxxxxxxxxxxxxxxxxxxxxx"
```

***

## 2. Create a sandbox backend

`UpstashBoxSandbox.create()` provisions a new box, waits until it is ready, and returns a backend that implements the Deep Agents `SandboxBackendProtocol`:

<Info>
The API key and base URL can also be passed directly as `api_key=` and `base_url=` arguments instead of the `UPSTASH_BOX_API_KEY` and `UPSTASH_BOX_BASE_URL` environment variables. The base URL defaults to `https://us-east-1.box.upstash.com`.
</Info>

```python
from langchain_upstash_box import UpstashBoxSandbox

# Creates a box and waits until it is ready.
sandbox = UpstashBoxSandbox.create(runtime="python")

result = sandbox.execute("echo hello")
print(result.output)     # "hello"
print(result.exit_code)  # 0

# Filesystem helpers (ls / read / write / edit / glob / grep) come for free.
sandbox.write("/workspace/home/hello.py", "print('hi from box')")
print(sandbox.execute("python3 /workspace/home/hello.py").output)

sandbox.delete()
```

If you already have a box, wrap it by id instead of creating a new one:

```python
sandbox = UpstashBoxSandbox(box_id="current-wasp-05510")
```

***

## 3. Use with a Deep Agent

Pass the sandbox as the agent's `backend`. Every shell command and file operation the agent performs now runs inside the box:

```python
from deepagents import create_deep_agent
from langchain_upstash_box import UpstashBoxSandbox

sandbox = UpstashBoxSandbox.create(runtime="python")

agent = create_deep_agent(
    model="anthropic:claude-sonnet-4-6",
    system_prompt="You are a coding assistant with sandbox access.",
    backend=sandbox,
)

result = agent.invoke(
    {
        "messages": [
            {"role": "user", "content": "Create a hello world Python script and run it"}
        ]
    }
)
print(result["messages"][-1].content)

sandbox.delete()
```

<Note>
The `model="anthropic:..."` shorthand requires `langchain-anthropic` (install with `pip install "langchain[anthropic]"`) and an `ANTHROPIC_API_KEY` environment variable. Any LangChain chat model works here.
</Note>

***

## 4. Cleanup

You own the box lifecycle, so call `sandbox.delete()` when you are done. An idle box pauses automatically: its compute is released but the filesystem is kept, and it wakes up on the next command. The box itself stays around until you delete it.

Commands have a default execution timeout of 30 minutes. You can override it for the whole backend or per call:

```python
sandbox = UpstashBoxSandbox.create(runtime="python", timeout=600)
result = sandbox.execute("sleep 5 && echo done", timeout=30)
```

To see everything else a box can do, like snapshots, git, previews, and schedules, check the [Box quickstart](/docs/box/overall/quickstart).

# Next.js Setup
Source: https://upstash.com/docs/box/guides/nextjs-setup

This guide walks you through scaffolding a [Next.js](https://nextjs.org) app inside an Upstash Box and opening it in your browser through a public URL, without using an agent.

The box gives you a full Linux environment with Node.js already installed, so you can create the app, run the dev server, and share it, all through the SDK.

***

## 1. Create a Box

Create a box with the `node` runtime. You don't need to configure an agent for this. See the [quickstart](/docs/box/overall/quickstart) if you haven't created one before.

<CodeGroup>
```typescript box.ts
import { Box } from "@upstash/box"

const box = await Box.create({ runtime: "node" })
```

```python box.py
from upstash_box import Box

box = Box.create(runtime="node")
```
</CodeGroup>

***

## 2. Scaffold the App

Run `create-next-app` inside the box. The `--yes` flag accepts the default options so the setup runs without prompts, installing all dependencies.

<CodeGroup>
```typescript box.ts
await box.exec.command("npx --yes create-next-app@latest my-next-app --yes")
```

```python box.py
box.exec.command("npx --yes create-next-app@latest my-next-app --yes")
```
</CodeGroup>

***

## 3. Start the Dev Server

Start the development server in the background so the command returns while Next.js keeps running on port `3000`. Commands run from the box's home directory (`/workspace/home`), where the app was created.

<CodeGroup>
```typescript box.ts
await box.exec.command("cd my-next-app && nohup npm run dev > dev.log 2>&1 &")
```

```python box.py
box.exec.command("cd my-next-app && nohup npm run dev > dev.log 2>&1 &")
```
</CodeGroup>

***

## 4. Create a Public URL

Expose port `3000` with a public URL to view the app in your browser. Creating a public URL for a running server returns its address.

<CodeGroup>
```typescript box.ts
const publicUrl = await box.getPublicURL(3000)

console.log(publicUrl.url)
// → https://<box-id>-3000.preview.box.upstash.com
```

```python box.py
public_url = box.get_public_url(3000)

print(public_url.url)
# -> https://<box-id>-3000.preview.box.upstash.com
```
</CodeGroup>

Open the URL in your browser and your Next.js app loads. See [Public URLs](/docs/box/overall/preview) for authentication and other options.

<Note>
  Next.js blocks cross-origin dev resources by default, so hot reloading won't work over the public URL until you add the host to `allowedDevOrigins` in `next.config.ts`:

  ```typescript next.config.ts
  const nextConfig: NextConfig = {
    allowedDevOrigins: ["<box-id>-3000.preview.box.upstash.com"],
  }
  ```
</Note>

# OpenClaw Setup
Source: https://upstash.com/docs/box/guides/openclaw-setup

This guide walks you through setting up [OpenClaw](https://docs.openclaw.ai) inside an Upstash Box.

***

## 1. Create a Box

Create a keep-alive box with default settings. See the [quickstart](/docs/box/overall/quickstart) if you haven't created one before.

<img />

***

## 2. Connect via SSH

Once the box is running, connect to it via SSH from your terminal. The `-L` flag forwards the OpenClaw dashboard port to your local machine. Use your [Box API key](/docs/box/overall/quickstart#1-get-your-api-key) as the password when prompted.

```bash
ssh -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -L 18789:127.0.0.1:18789 <box-id>@us-east-1.box.upstash.com
```

The `box-id` is the name of your box (e.g. `right-flamingo-14486`).

***

## 3. Install OpenClaw

Install the OpenClaw CLI globally inside the box.

```bash
sudo npm install -g openclaw
```

***

## 4. Run Onboarding

Start the interactive onboarding wizard. The CLI will guide you through provider selection, authentication, and gateway configuration.

```bash
openclaw onboard --install-daemon
```

Follow the prompts to complete your setup. Once onboarding is complete, copy the dashboard URL with its token — you will need it in step 6.

<img />

***

## 5. Start the Gateway

Configure the gateway to bind on the LAN interface, then start it.

```bash
openclaw config set gateway.bind lan
nohup openclaw gateway > gateway.log 2>&1 &
```

***

## 6. Open the Dashboard

Navigate to the dashboard URL you copied in step 4. Because the SSH tunnel is active, the dashboard is accessible on your local machine and will look like:

```
http://127.0.0.1:18789/#token=<your-token>
```

Congratulations! You have successfully set up OpenClaw on your Upstash Box. Your AI gateway is now running and accessible through the secure SSH tunnel.

<img />

***

## 7. Set Init Script for Auto-Restart

To ensure the gateway restarts automatically if the box crashes, set the startup command as an init script from the Upstash Console.

<img />

The init script to use:

```bash
nohup openclaw gateway > gateway.log 2>&1 &
```

This command runs automatically whenever the box starts, so your gateway is always available without manual intervention.

***

## Troubleshooting

If your SSH session freezes randomly during onboarding, retry the connection with a clean config and no shared control socket:

```bash
ssh -F /dev/null -o ControlMaster=no -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -L 18789:127.0.0.1:18789 <box-id>@us-east-1.box.upstash.com
```

The most likely causes are a local `~/.ssh/config` entry (e.g. a stale `ControlMaster` socket or conflicting options) being applied to the connection, or a NAT/firewall on your network dropping idle TCP connections. The flags above bypass your local SSH config and keep the connection active with periodic keepalives.

# Pi Setup
Source: https://upstash.com/docs/box/guides/pi-setup

This guide walks you through using [Pi](https://pi.dev) with the [`@upstash/box-pi`](https://pi.dev/packages/@upstash/box-pi) extension ([npm](https://www.npmjs.com/package/@upstash/box-pi)), which runs every tool call (`bash`, file I/O, and search) inside an Upstash Box. The agent and your model API keys stay on your machine; only tool execution happens in the box.

<Note>
  This is the inverse of running Pi *inside* a box as a [custom harness](https://github.com/upstash/box/blob/main/packages/sdk/examples/custom-pi-agent.ts). Here, Pi runs locally and only its tools execute remotely.
</Note>

***

## 1. Get your API key

Go to the [Upstash Console](https://console.upstash.com) and create a Box API key. See the [quickstart](/docs/box/overall/quickstart#1-get-your-api-key) for details.

***

## 2. Install Pi and the extension

```bash
npm install -g @earendil-works/pi-coding-agent
pi install npm:@upstash/box-pi
```

<Note>
  To update the extension later, run `pi update`. `pi install` won't refresh an existing install.
</Note>

***

## 3. Set the API key

Export your Box API key (e.g. in your shell profile):

```bash
export UPSTASH_BOX_API_KEY="box_xxxxxxxxxxxxxxxxxxxxxxxx"
```

If no key is set, Pi prompts you for one once per session.

***

## 4. Run Pi with `--box`

Run Pi from inside a git repository whose `origin` is on GitHub:

```bash
cd my-project
pi --box
```

The repo is cloned **from GitHub** into a fresh box. The box never sees your local files, so the repo must exist on github.com (private is fine) and anything you want in the box must be committed and pushed. The session's work syncs to its own GitHub branch (see step 5). You can also point at another repo, or run outside a git repo for a blank workspace:

```bash
pi --box --repo github.com/acme/api --branch dev
```

A footer badge in Pi shows that tool calls are running remotely. Everything else works exactly like a local Pi session: the agent reads, writes, and executes in the box transparently.

### Flags

| Flag               | Description                                                     |
| ------------------ | --------------------------------------------------------------- |
| `--box`            | Run tools inside an Upstash Box sandbox                         |
| `--repo <url>`     | Git repo to clone into the box (defaults to the repo you're in) |
| `--branch <name>`  | Branch to clone (defaults to your current branch)               |
| `--runtime <name>` | Box runtime image: `node`, `python`, `golang`, `ruby`, `rust` (append `-alpine` for the musl variant) |
| `--size <name>`    | Box size: `small` (default), `medium`, or `large`               |

***

## 5. Sync work to GitHub

If you launched Pi in a **github.com** repo and you're logged in with the GitHub CLI (`gh auth login`), each session gets its own branch: the extension creates `pi/<short-session-id>` off your current branch, the agent commits its work, and the extension pushes those commits after each turn.

Manage the branch with slash commands inside Pi:

| Command    | What it does                                          |
| ---------- | ----------------------------------------------------- |
| `/sandbox` | Show the active box's status and its branch link      |
| `/github`  | Open this session's branch on GitHub                  |
| `/compare` | Open the branch compare view on GitHub                |
| `/merge`   | Merge this session's branch into its base             |
| `/pr`      | Create a pull request for this session's branch       |

<Note>
  Outside a github.com repo (or without `gh` authenticated), push is disabled. The box still has a local git repo, so the agent can commit, but nothing leaves the box.
</Note>

***

## 6. Preview a running server

When the agent starts a server in the box, ask it for a preview link. The `preview_url` tool returns a live, basic-auth-protected URL for any port:

```
https://<box-name>-8080.preview.box.upstash.com
```

The login is printed alongside the URL, and your browser prompts for it. Preview URLs are created fresh on every call, so ask again after a box resumes from idle. See [Preview URLs](/docs/box/overall/preview) for how these work.

***

## 7. Session lifecycle

Each Pi session gets one box, and the box follows the session:

* **Resume a session**, and its box is reattached, with your files and environment exactly where you left them.
* **Idle sessions** pause the box automatically with the filesystem preserved; the next tool call transparently restarts it. Nothing to configure.
* **Delete a session** from Pi's resume menu, and its box is deleted too. A box lives as long as its session.

***

## Troubleshooting

* **The box is empty.** The box clones from GitHub; it never uploads local files. Make sure the repo exists on github.com and your checkout has an `origin` remote, then start a new session. Run `/sandbox` to see what was cloned.
* **Local changes missing in the box?** Commit and push first. The box clones the branch from GitHub, not your working tree.
* **Duplicate flag or tool errors** mean two copies of the extension are loaded. Run `pi list` and `pi uninstall` one of them.
* **Pushes not appearing on GitHub?** Branch sync needs `gh auth login` and a github.com remote.

# Remote Development
Source: https://upstash.com/docs/box/guides/remote-development

Upstash Box supports full remote development workflows. By forwarding ports over SSH, you can run any web server inside a box and access it in your local browser exactly as if it were running on your machine. You can also expose it to the internet with a public URL for sharing or collaboration.

This guide uses Next.js as an example, but the same approach works for any dev server; Vite, Django, Rails, Express, and so on.

***

## 1. Create a Box

Create a box with default settings. See the [quickstart](/docs/box/overall/quickstart) if you haven't created one before.

<img />

***

## 2. Connect via SSH with Port Forwarding

Connect to your box via SSH with the `-L` flag to forward a port from the box to your local machine. Replace `3000` with whichever port your dev server listens on.

The `box-id` is the name of your box (e.g. `right-flamingo-14486`).

```bash
ssh -L 3000:127.0.0.1:3000 <box-id>@us-east-1.box.upstash.com
```

Use your [Box API key](/docs/box/overall/quickstart#1-get-your-api-key) as the password when prompted.

<img />

***

## 3. Start Your Dev Server

Inside the box, start your application. You can set it up manually or ask the built-in agent to build one for you. For example, with Next.js:

```bash
npx create-next-app@latest my-app
cd my-app
npm run dev
```

Because the SSH tunnel is active, your app is immediately accessible in your local browser at:

```
http://localhost:3000
```

You can edit files inside the box and see changes live, just as you would in a local development environment.

***

## 4. Share with a Public URL (Optional)

If you want to share your app with others, you can expose a port with a public URL from the [Upstash Console](https://console.upstash.com). See the [Public URLs](/docs/box/overall/preview) guide for details.

Your app will be accessible at:

```
https://<box-id>-3000.preview.box.upstash.com
```

<img />

# AI File Editor with TanStack AI
Source: https://upstash.com/docs/box/guides/tanstack-ai-file-editor

An `EphemeralBox` gives you exactly two capabilities: **code execution** and **file operations**. That turns out to be all you need to hand an AI agent a real, disposable computer. This guide uses both. The model writes the code, and a single tool moves files in and out of the sandbox to run it against.

We'll build a file editor with [TanStack AI](https://tanstack.com/ai). The user uploads a file and asks for a change. The model writes the code to do it. A single tool then **spins up a Box, uploads the file, runs that code, downloads the result, and tears the Box down**. The model's code and its dependencies never touch your server. They run inside an isolated sandbox that exists only for the length of the call.

***

## 1. Installation

```bash
npm install @tanstack/ai @tanstack/ai-anthropic @tanstack/ai-react @upstash/box zod
```

Get a Box API key from the [Upstash Console](https://console.upstash.com/box) and add your environment variables:

```bash title=".env.local"
UPSTASH_BOX_API_KEY=box_xxxxxxxxxxxxxxxxxxxxxxxx
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxx
```

***

## 2. The API route

The route hands the model a single `editFile` tool. Each time the model calls it, the tool runs one self-contained lifecycle: it **creates a fresh `EphemeralBox`, uploads the user's file, runs the model's code, finds the result with `files.list`, reads it back, and deletes the Box in a `finally`**. The model writes code that reads the input file and drops its result into an output directory. Those four calls (`files.write`, `exec`, `files.list`, and `files.read`) are the entire `EphemeralBox` surface, and they're enough to drive a real feature.

```typescript title="app/api/chat/route.ts"
import { chat, toServerSentEventsResponse, toolDefinition } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
import { EphemeralBox } from "@upstash/box";
import { z } from "zod";

const OUT_DIR = "/workspace/home/out";

const MIME: Record<string, string> = {
  png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif",
  webp: "image/webp", json: "application/json", csv: "text/csv",
  txt: "text/plain", md: "text/markdown", pdf: "application/pdf",
  mp3: "audio/mpeg", wav: "audio/wav",
};
const mimeOf = (name: string) => MIME[name.split(".").pop()?.toLowerCase() ?? ""] ?? "application/octet-stream";

export async function POST(request: Request) {
  const { messages, forwardedProps } = await request.json();
  const file = forwardedProps?.file; // useChat forwards the connection `body` here

  // The browser sends { name, dataUrl }; keep the base64 payload + a safe name.
  const base64 = String(file?.dataUrl ?? "").replace(/^data:.*;base64,/, "");
  const inputName = String(file?.name ?? "input").replace(/[^a-zA-Z0-9._-]/g, "_");
  const inputPath = `/workspace/home/${inputName}`;

  const editFile = toolDefinition({
    name: "editFile",
    description:
      "Transform the uploaded file by running Python. Your code reads the input file and " +
      `writes its result into ${OUT_DIR}/ with an appropriate extension.`,
    inputSchema: z.object({
      code: z.string().describe(`Python that reads the input file and writes the result into ${OUT_DIR}/.`),
    }),
    outputSchema: z.object({
      ok: z.boolean(),
      stdout: z.string(),
      file: z
        .object({ name: z.string(), mediaType: z.string(), base64: z.string() })
        .nullable(),
    }),
  }).server(async ({ code }) => {
    // No file uploaded? Don't even spin up a box.
    if (!base64) {
      return { ok: false, stdout: "No file has been uploaded yet.", file: null };
    }

    // A fresh sandbox per call: created here, torn down in `finally`.
    const box = await EphemeralBox.create({
      apiKey: process.env.UPSTASH_BOX_API_KEY,
      runtime: "python",
      ttl: 120, // safety net in case `delete()` is skipped
    });

    try {
      // Prepare the box: output dir + libraries. (For production, bake these into a snapshot.)
      await box.exec.command(`mkdir -p ${OUT_DIR} && pip install -q pillow pandas pypdf`);

      // UPLOAD: the user's file goes into the sandbox (file op)
      await box.files.write({ path: inputPath, content: base64, encoding: "base64" });

      // EXEC: run the Python the model wrote
      const run = await box.exec.code({ lang: "python", code });
      if (run.exitCode !== 0) {
        return { ok: false, stdout: run.result, file: null };
      }

      // LIST: discover what the code produced (file op)
      const entries = await box.files.list(OUT_DIR);
      const out = entries.find((e) => !e.is_dir);
      if (!out) {
        return { ok: false, stdout: run.result + "\n(no output file produced)", file: null };
      }

      // DOWNLOAD: read the result back out as base64 (file op)
      const result = await box.files.read(out.path, { encoding: "base64" });
      return {
        ok: true,
        stdout: run.result,
        file: { name: out.name, mediaType: mimeOf(out.name), base64: result },
      };
    } finally {
      await box.delete();
    }
  });

  const stream = chat({
    adapter: anthropicText("claude-sonnet-4-6"),
    modelOptions: { max_tokens: 2048 },
    systemPrompts: [
      "You are a file editor backed by a secure sandbox. " +
        "Reply normally to greetings, questions, and small talk WITHOUT calling any tool. " +
        "Only call the editFile tool when the user actually asks you to transform or edit their uploaded file. " +
        `The uploaded file, when present, is at ${inputPath}. To edit it, write Python that reads it, applies ` +
        `the change, and saves the result into ${OUT_DIR}/ with a sensible filename and extension, then call ` +
        "editFile with that code. Libraries available: Pillow, pandas, numpy, pypdf, plus the standard library (e.g. `wave` for audio). " +
        "After the tool succeeds, tell the user in one short sentence what you did.",
    ],
    messages,
    tools: [editFile],
  });

  return toServerSentEventsResponse(stream);
}
```

<Note>
The file tools (`files.write`, `files.list`, and `files.read`) are rooted at **`/workspace/home`**, and paths outside it are rejected. `exec` can write anywhere, but anything you move through the files API has to live under `/workspace/home`. That's why both the input file and `OUT_DIR` sit there.
</Note>

Because the model writes the code and we find the output with `files.list`, nothing here is image-specific. Ask for "convert this CSV to JSON" and the model uses pandas to write `out/data.json`, and the tool reads it back the same way. To support more formats, add their libraries to the `pip install` line, or bake them into a snapshot.

***

## 3. The UI

Wire up `useChat` from `@tanstack/ai-react`. The uploaded file rides along on the request through the connection's dynamic `body` option. TanStack AI delivers that payload to the server under **`forwardedProps`**, which is why the route reads `forwardedProps.file` instead of a top-level field. We keep the file in a ref so each send picks up the latest one. Tool-call parts carry the typed `output`, so we render images inline and show a download link for everything else. A plain text prompt with no file is just a normal chat turn.

```tsx title="app/page.tsx"
"use client";

import { useRef, useState } from "react";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";

type ResultFile = { name: string; mediaType: string; base64: string };

export default function Page() {
  const [input, setInput] = useState("");
  const [fileName, setFileName] = useState<string | null>(null);
  const fileRef = useRef<{ name: string; dataUrl: string } | null>(null);

  const { messages, sendMessage, isLoading } = useChat({
    // The function runs per request, so it always sends the current file.
    connection: fetchServerSentEvents("/api/chat", () => ({
      body: { file: fileRef.current },
    })),
  });

  function handleFile(e: React.ChangeEvent<HTMLInputElement>) {
    const f = e.target.files?.[0];
    if (!f) return;
    const reader = new FileReader();
    reader.onload = () => {
      fileRef.current = { name: f.name, dataUrl: reader.result as string };
      setFileName(f.name);
    };
    reader.readAsDataURL(f);
  }

  function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    if (isLoading || !input.trim()) return; // a file is only needed for edits
    sendMessage(input.trim());
    setInput("");
  }

  function renderResult(file: ResultFile, key: number) {
    const src = `data:${file.mediaType};base64,${file.base64}`;
    if (file.mediaType.startsWith("image/")) {
      return <img key={key} src={src} alt={file.name} className="max-h-72 rounded border" />;
    }
    return (
      <a key={key} href={src} download={file.name} className="text-sm text-blue-600 underline">
        ⬇ Download {file.name}
      </a>
    );
  }

  return (
    <div className="mx-auto flex h-screen max-w-2xl flex-col gap-4 p-4">
      <h1 className="text-lg font-semibold">AI File Editor</h1>

      <input type="file" onChange={handleFile} />
      {fileName && <p className="text-xs text-gray-500">Selected: {fileName}</p>}

      <div className="flex-1 space-y-4 overflow-y-auto">
        {messages.map((message) => (
          <div key={message.id}>
            <div className="text-xs font-medium text-gray-500">
              {message.role === "user" ? "You" : "Editor"}
            </div>
            {message.parts.map((part, i) => {
              if (part.type === "text") {
                return (
                  <p key={i} className="whitespace-pre-wrap text-sm">
                    {part.content}
                  </p>
                );
              }
              if (part.type === "tool-call") {
                const result = part.output as
                  | { ok: boolean; file: ResultFile | null }
                  | undefined;
                if (!result) return <p key={i} className="text-xs text-gray-400">editing…</p>;
                if (result.ok && result.file) return renderResult(result.file, i);
                return <p key={i} className="text-xs text-red-500">edit failed</p>;
              }
              return null;
            })}
          </div>
        ))}
      </div>

      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="e.g. make it black and white"
          disabled={isLoading}
          className="flex-1 rounded border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-gray-400"
        />
        <button
          type="submit"
          disabled={isLoading}
          className="rounded bg-black px-4 py-2 text-sm text-white disabled:opacity-40"
        >
          {isLoading ? "Editing…" : "Edit"}
        </button>
      </form>
    </div>
  );
}
```

***

## 4. Try it

Run your Next.js app, upload a file, and describe the edit. With a color photo and *"make it black and white,"* the model writes a few lines of Pillow:

```python
from PIL import Image
img = Image.open("/workspace/home/photo.png").convert("L")
img.save("/workspace/home/out/photo.png")
print("converted to grayscale")
```

The `editFile` tool runs it, finds `out/photo.png` via `files.list`, reads it back as base64, and the UI renders the grayscale image under your message.

Swap the input and the prompt, and the same path carries the feature end to end:

* **CSV to JSON:** ask *"convert this to JSON"* and the model uses pandas to write `out/data.json`, which the UI shows as a download link.
* **Uppercase the headings in a Markdown file, resize an image, or extract a page from a PDF.** Anything the model can express in Python with the installed libraries works.

Through all of this, the untrusted model-generated code and its dependencies stayed inside an isolated `EphemeralBox` that was created for the call and deleted the moment it returned. The whole editor is just two `EphemeralBox` capabilities wired into one tool: `exec`, plus the file operations `write`, `list`, and `read`.

# Scrape Dynamic Websites with Playwright
Source: https://upstash.com/docs/box/guides/web-scraping-playwright

In this guide, we use Upstash Box to run [Playwright](https://playwright.dev) against a JavaScript-heavy site, scrape structured data from it, and pull the results back to our own server. Because a Box is a real Linux container rather than a restricted serverless runtime, Chromium and its system dependencies install and run exactly like they would on your laptop.

<Note>
  Boxes also ship a managed, pre-installed browser. Create one with `browser:
  true` and drive Chromium through the SDK, or [connect Playwright over
  CDP](/docs/box/overall/browser/connect) with no install step. This guide shows
  the manual route, where the box's agent installs and controls Playwright
  itself. See [Browser](/docs/box/overall/browser/overview) for the built-in one.
</Note>

***

## 1. Installation

```bash
npm install @upstash/box
```

Set your environment variables:

```bash title=".env"
UPSTASH_BOX_API_KEY=box_xxxxxxxxxxxxxxxxxxxxxxxx
```

***

## 2. Provision a box and install Playwright

Create a box with outbound network access (the default) so it can reach the target site and download the browser binaries, then install Playwright and Chromium with its system dependencies.

```typescript title="scripts/scrape.ts"
import "dotenv/config"
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  agent: {
    harness: Agent.ClaudeCode,
    model: "anthropic/claude-sonnet-4-6",
  },
})

console.log(`Box ready: ${box.id}`)

await box.exec.command("npm init -y && npm install playwright")

// `--with-deps` pulls in the Linux system libraries Chromium needs via apt-get
const setup = await box.exec.command("npx playwright install chromium --with-deps")

if (setup.status !== "completed") {
  throw new Error(`Chromium setup failed: ${setup.result}`)
}

console.log("Chromium and its system dependencies are ready.")
```

***

## 3. Let the agent write and run the scraper

Hand the scraping task to the box's built-in agent. It writes the Playwright script, runs it, fixes any issues it hits along the way, and saves the output to a file in the workspace.

```typescript title="scripts/scrape.ts" {1}
const run = await box.agent.run({
  prompt: `
Write a Node.js script that uses Playwright to:
1. Launch headless Chromium and navigate to https://news.ycombinator.com/show
2. Wait for the page to finish loading
3. Extract the title, URL, and point count for the top 10 posts
4. Save the result as a JSON array to /workspace/home/scraped_data.json

Then run the script and confirm the file was written successfully.
  `.trim(),
})

console.log(run.result)
```

The agent has shell, filesystem, and the installed Playwright package available, so it can iterate — adjusting selectors, adding waits for dynamic content, retrying on failure — until the scrape actually produces data.

***

## 4. Pull the results back

Read the file the agent wrote and bring it back into your own process.

```typescript title="scripts/scrape.ts"
const raw = await box.files.read("/workspace/home/scraped_data.json")
const dataset = JSON.parse(raw)

console.table(dataset.slice(0, 3))

await box.delete()
```

You now have structured data extracted from a dynamic, JavaScript-rendered page — without managing a single Chromium binary yourself.

***

## 5. Skip the setup on every run with snapshots

`npx playwright install chromium --with-deps` takes real time to stream and unpack OS-level packages. Paying that cost on every scrape request would be painful in production.

[Snapshot](/docs/box/overall/snapshots) the box once Chromium and its dependencies are installed, and restore from that snapshot whenever you need a ready-to-go scraping environment:

```typescript title="scripts/prepare-snapshot.ts"
const snapshot = await box.snapshot({ name: "playwright-ready" })
console.log(`Snapshot ready: ${snapshot.id}`)
```

Store `snapshot.id` somewhere your application can reach (an env var, a database row, etc.), then spin up pre-warmed boxes from it on demand:

```typescript title="scripts/run-scrape-job.ts"
import { Box } from "@upstash/box"

const box = await Box.fromSnapshot(process.env.PLAYWRIGHT_SNAPSHOT_ID!)

const run = await box.agent.run({
  prompt: "Navigate to <url> and extract <data>...",
})

await box.delete()
```

Restoring from a snapshot starts the box with Chromium and its system libraries already in place, so the agent can start scraping immediately instead of waiting on `apt-get` and binary downloads.

# Agent
Source: https://upstash.com/docs/box/overall/agent

**For every box, you can configure a built-in agent like Claude Code, Codex or OpenCode**. It has access to the filesystem, git, and shell commands and should simulate running an Agent on your own computer.

You can choose between:

* `run()` when you want to wait for completion and then read the final typed result.
* `stream()` when you want real-time output while the agent is running.

***

## Configure an Agent

<Tabs>
<Tab title="Claude">

Get your Claude API key from the [Claude Console](https://platform.claude.com/settings/keys).

```bash title=".env" {2}
UPSTASH_BOX_API_KEY=box_xxxxxxxxxxxxxxxxxxxxxxxx
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxx
```

<CodeGroup>
```tsx box.ts {5-8}
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  agent: {
    harness: Agent.ClaudeCode,
    model: "anthropic/claude-sonnet-4-5",
    apiKey: process.env.ANTHROPIC_API_KEY!,
  },
})
```

```python box.py
import os
from upstash_box import Box, Agent

box = Box.create(
    runtime="node",
    agent={
        "harness": Agent.CLAUDE_CODE,
        "model": "anthropic/claude-sonnet-4-5",
        "api_key": os.environ["ANTHROPIC_API_KEY"],
    },
)
```
</CodeGroup>

</Tab>
<Tab title="OpenCode">

```bash title=".env" {2}
UPSTASH_BOX_API_KEY=box_xxxxxxxxxxxxxxxxxxxxxxxx
OPENCODE_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxx
```

<CodeGroup>
```tsx box.ts {5-8}
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  agent: {
    harness: Agent.OpenCode,
    model: "opencode/claude-sonnet-4-6",
    apiKey: process.env.OPENCODE_API_KEY!,
  },
})
```

```python box.py
import os
from upstash_box import Box, Agent

box = Box.create(
    runtime="node",
    agent={
        "harness": Agent.OPEN_CODE,
        "model": "opencode/claude-sonnet-4-6",
        "api_key": os.environ["OPENCODE_API_KEY"],
    },
)
```
</CodeGroup>

</Tab>
<Tab title="Codex">

```bash title=".env" {2}
UPSTASH_BOX_API_KEY=box_xxxxxxxxxxxxxxxxxxxxxxxx
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxx
```

<CodeGroup>
```tsx box.ts {5-8}
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  agent: {
    harness: Agent.Codex,
    model: "openai/gpt-5.3-codex",
    apiKey: process.env.OPENAI_API_KEY!,
  },
})
```

```python box.py
import os
from upstash_box import Box, Agent

box = Box.create(
    runtime="node",
    agent={
        "harness": Agent.CODEX,
        "model": "openai/gpt-5.3-codex",
        "api_key": os.environ["OPENAI_API_KEY"],
    },
)
```
</CodeGroup>

</Tab>
</Tabs>

### Agent Options

You can pass provider-specific agent options with the `options` field on `box.agent.run()` and `box.agent.stream()`.

<CodeGroup>
```tsx box.ts
const run = await box.agent.run({
  prompt: "Refactor the auth flow and keep changes minimal",
  options: {
    effort: "medium",
    maxTurns: 12,
  },
})
```

```python box.py
run = box.agent.run(
    prompt="Refactor the auth flow and keep changes minimal",
    options={
        "effort": "medium",
        "max_turns": 12,
    },
)
```
</CodeGroup>

The exact option shape depends on the configured agent:

* `ClaudeCode`: `maxTurns`, `maxBudgetUsd`, `effort`, `thinking`, `disallowedTools`, `agents`, `promptSuggestions`, `fallbackModel`, `systemPrompt`
* `Codex`: `modelReasoningEffort`, `modelReasoningSummary`, `personality`, `webSearch`
* `OpenCode`: `reasoningEffort`, `textVerbosity`, `reasoningSummary`, `thinking`

<Note>
  In the Python SDK these option keys are **snake_case** — e.g. `max_turns`, `max_budget_usd`, `model_reasoning_effort`, `web_search`, `reasoning_effort`.
</Note>

To bring your own agent process, use a [custom agent](/docs/box/overall/custom-agent).

## Quickstart

<Tabs>
<Tab title="Refactor Code">

### Codebase refactor

Use Claude Code when you want an agent to work through a larger code change with filesystem, shell, and git access.

<CodeGroup>
```tsx box.ts
const stream = await box.agent.stream({
  prompt: `
Refactor the payment module to:
- move shared validation into src/payment/validation.ts
- keep the public API unchanged
- update tests if needed
- summarize the main risks before finishing
  `,
})

for await (const chunk of stream) {
  if (chunk.type === "text-delta") process.stdout.write(chunk.text)
}

console.log(stream.status)
console.log(stream.result)
```

```python box.py
stream = box.agent.stream(
    prompt="""
Refactor the payment module to:
- move shared validation into src/payment/validation.ts
- keep the public API unchanged
- update tests if needed
- summarize the main risks before finishing
""",
)

for chunk in stream:
    if chunk.type == "text-delta":
        print(chunk.text, end="")

print(stream.status)
print(stream.result)
```
</CodeGroup>

</Tab>
<Tab title="Review Changes">

### Fast code review summary

Use OpenCode when you want a concise review or summary over an existing diff or repository state.

<CodeGroup>
```tsx box.ts
const run = await box.agent.run({
  prompt: `
Review the current git diff and return:
- the top 3 risks
- missing test coverage
- whether the change looks safe to merge
  `,
})

console.log(run.result)
console.log(run.cost.totalUsd)
```

```python box.py
run = box.agent.run(
    prompt="""
Review the current git diff and return:
- the top 3 risks
- missing test coverage
- whether the change looks safe to merge
""",
)

print(run.result)
print(run.cost.total_usd)
```
</CodeGroup>

</Tab>
<Tab title="Structured Output">

### Structured analysis

Use Codex when you want a strongly structured response that you can feed into another system.

<CodeGroup>
```tsx box.ts
import { z } from "zod"

const result = await box.agent.run({
  prompt: "Analyze /work/report.csv and return the top 10 customers by revenue",
  responseSchema: z.object({
    customers: z.array(
      z.object({
        name: z.string(),
        revenue: z.number(),
      }),
    ),
  }),
})

console.log(result.result.customers)
```

```python box.py
from pydantic import BaseModel

class Customer(BaseModel):
    name: str
    revenue: float

class Result(BaseModel):
    customers: list[Customer]

result = box.agent.run(
    prompt="Analyze /work/report.csv and return the top 10 customers by revenue",
    response_schema=Result,
)

print(result.result.customers)
```
</CodeGroup>

</Tab>
<Tab title="Open a PR">

Clone a repository, run the agent, and open a pull request.

<CodeGroup>
```tsx box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  agent: {
    harness: Agent.ClaudeCode,
    model: "anthropic/claude-opus-4-5",
    apiKey: process.env.ANTHROPIC_API_KEY,
  },
  git: {
    token: process.env.GITHUB_TOKEN,
  },
})

await box.git.clone({ repo: "github.com/your-org/your-repo" })

const stream = await box.agent.stream({
  prompt: "Fix the null token bug in src/auth.ts and add tests",
})

for await (const chunk of stream) {
  if (chunk.type === "text-delta") process.stdout.write(chunk.text)
}

await box.git.createPR({
  title: "Fix null token bug",
  base: "main",
})
```

```python box.py
import os
from upstash_box import Box, Agent

box = Box.create(
    runtime="node",
    agent={
        "harness": Agent.CLAUDE_CODE,
        "model": "anthropic/claude-opus-4-5",
        "api_key": os.environ["ANTHROPIC_API_KEY"],
    },
    git={"token": os.environ["GITHUB_TOKEN"]},
)

box.git.clone(repo="github.com/your-org/your-repo")

stream = box.agent.stream(prompt="Fix the null token bug in src/auth.ts and add tests")

for chunk in stream:
    if chunk.type == "text-delta":
        print(chunk.text, end="")

box.git.create_pr(title="Fix null token bug", base="main")
```
</CodeGroup>

</Tab>
<Tab title="Process Documents">

Run one box per file, process in parallel, and return ranked structured results.

<CodeGroup>
```tsx box.ts
import { Agent, Box } from "@upstash/box"
import { readdir } from "fs/promises"
import { z } from "zod"

const responseSchema = z.object({
  name: z.string(),
  email: z.string(),
  yearsOfExperience: z.number(),
  skills: z.array(z.string()),
  score: z.number().min(0).max(100),
  summary: z.string(),
})

const job = "Senior Backend Engineer (Node.js, PostgreSQL)."

const files = await readdir("./resumes")
const resumes = files.filter((file) => file.endsWith(".pdf"))

const results = await Promise.all(
  resumes.map(async (file) => {
    const box = await Box.create({
      runtime: "node",
      agent: {
        model: "anthropic/claude-opus-4-5",
        apiKey: process.env.ANTHROPIC_API_KEY,
      },
    })

    await box.files.upload([
      { path: `./resumes/${file}`, destination: "/work/resume.pdf" },
    ])

    const run = await box.agent.run({
      prompt: `Read /work/resume.pdf. Extract candidate data and score 0-100 for: ${job}`,
      responseSchema,
    })

    await box.delete()

    return { file, ...run.result, cost: run.cost.totalUsd }
  }),
)

const ranked = results.sort((a, b) => b.score - a.score)
```

```python box.py
import asyncio
import os
from pydantic import BaseModel, Field
from upstash_box import AsyncBox

class Candidate(BaseModel):
    name: str
    email: str
    years_of_experience: int
    skills: list[str]
    score: int = Field(ge=0, le=100)
    summary: str

job = "Senior Backend Engineer (Node.js, PostgreSQL)."

resumes = [f for f in os.listdir("./resumes") if f.endswith(".pdf")]

async def process(file: str) -> dict:
    box = await AsyncBox.create(
        runtime="node",
        agent={"model": "anthropic/claude-opus-4-5", "api_key": os.environ["ANTHROPIC_API_KEY"]},
    )
    await box.files.upload([{"path": f"./resumes/{file}", "destination": "/work/resume.pdf"}])
    run = await box.agent.run(
        prompt=f"Read /work/resume.pdf. Extract candidate data and score 0-100 for: {job}",
        response_schema=Candidate,
    )
    await box.delete()
    return {"file": file, "candidate": run.result, "cost": run.cost.total_usd}

async def main() -> list:
    results = await asyncio.gather(*(process(f) for f in resumes))
    return sorted(results, key=lambda r: r["candidate"].score, reverse=True)

ranked = asyncio.run(main())
```
</CodeGroup>

</Tab>
</Tabs>

***

## API

### Prompt (required)

Type: `string`
Supported on: `box.agent.run()` and `box.agent.stream()`

The task instruction sent to the agent.

### Options

Type: `AgentOptions`
Supported on: `box.agent.run()`, `box.agent.stream()`, and `box.schedule.agent()`

Provider-specific agent options forwarded to the underlying runner.

<CodeGroup>
```tsx box.ts
const stream = await box.agent.stream({
  prompt: "Review the latest git diff and summarize risks",
  options: {
    effort: "high",
    maxTurns: 20,
  },
})
```

```python box.py
stream = box.agent.stream(
    prompt="Review the latest git diff and summarize risks",
    options={
        "effort": "high",
        "max_turns": 20,
    },
)
```
</CodeGroup>

### Timeout

Type: `number`
Supported on: `box.agent.run()`, `box.agent.stream()`, and `box.schedule.agent()`
Default: no execution timeout

Execution timeout in milliseconds. When reached, the run is aborted.

### onToolUse

Type: `{ name: string; input: Record<string, unknown> }`
Supported on: `box.agent.run()` and `box.agent.stream()`

Called whenever the agent invokes a tool (for example file, shell, or git tools).

### responseSchema

Type: `Zod Schema`
Supported on: `box.agent.run()`

Attach a Zod schema to get typed output.

<CodeGroup>
```tsx box.ts
import { z } from "zod"

const responseSchema = z.object({
  customers: z.array(
    z.object({
      name: z.string(),
      revenue: z.number(),
    }),
  ),
})

const analysis = await box.agent.run({
  prompt: "Analyze /work/report.csv and return top customers by revenue",
  responseSchema,
})

console.log(analysis.result.customers)
```

```python box.py
from pydantic import BaseModel

class Customer(BaseModel):
    name: str
    revenue: float

class Analysis(BaseModel):
    customers: list[Customer]

analysis = box.agent.run(
    prompt="Analyze /work/report.csv and return top customers by revenue",
    response_schema=Analysis,
)

print(analysis.result.customers)
```
</CodeGroup>

### maxRetries

Type: `number`
Supported on: `box.agent.run()`
Default: `0`

Retry count to compensate temporary provider outages or similar transient errors. Retries use exponential backoff (`1s`, `2s`, `4s`, ...) capped at `30s`.

### Webhook

Type: `WebhookConfig`
Supported on: `box.agent.run()`

Useful for fire-and-forget mode. The SDK returns immediately and sends the completion payload to your webhook URL when the run succeeds or fails.

***

# Attach Headers
Source: https://upstash.com/docs/box/overall/attach-headers

`attachHeaders` lets you inject HTTP headers into outbound HTTPS requests from a box without the secrets ever entering the container. This is useful for API keys, bearer tokens, and other credentials that should not be readable by code running inside the box.

## How it works

When a box is created with attach headers, a TLS-intercepting proxy on the host injects the configured headers into outbound HTTPS requests that match the configured host patterns.

* The secrets stay on the host
* They do not appear in environment variables, files, or process memory inside the container
* Traffic to hosts that do not match any rule passes through untouched

## Global attach headers

You can configure attach headers at the user or team level in the console Settings tab under **Attach Headers**. These global headers are applied automatically to new boxes.

When a box is created, global and per-box headers are merged:

* Per-box headers override global headers for the same host pattern
* Global headers for host patterns not specified per-box are included as-is

## Per-box attach headers

You can also configure attach headers per box with the SDK:

<CodeGroup>
```typescript box.ts
const box = await Box.create({
  runtime: "node",
  attachHeaders: {
    "api.stripe.com": {
      Authorization: "Bearer sk_live_...",
    },
    "*.supabase.co": {
      apikey: "eyJ...",
    },
    "api.anthropic.com": {
      "x-api-key": "sk-ant-...",
    },
  },
})
```

```python box.py
box = Box.create(
    runtime="node",
    attach_headers={
        "api.stripe.com": {
            "Authorization": "Bearer sk_live_...",
        },
        "*.supabase.co": {
            "apikey": "eyJ...",
        },
        "api.anthropic.com": {
            "x-api-key": "sk-ant-...",
        },
    },
)
```
</CodeGroup>

Any HTTPS request from inside the box to a matching host will automatically include the configured headers.

<CodeGroup>
```typescript box.ts
const result = await box.exec.command(
  'curl -s https://api.stripe.com/v1/charges?limit=1'
)
```

```python box.py
result = box.exec.command(
    "curl -s https://api.stripe.com/v1/charges?limit=1"
)
```
</CodeGroup>

The `Authorization` header is added by the proxy. The container never sees the secret itself.

## Host patterns

| Pattern | Matches |
|---|---|
| `api.stripe.com` | Exact match only |
| `*.supabase.co` | Any subdomain such as `xyz.supabase.co` or `db.supabase.co` |

* Patterns must be lowercase
* Only `*.` prefix wildcards are supported
* Most-specific match wins

## When to use attach headers vs env vars

| | Environment Variables | Attach Headers |
|---|---|---|
| **Visibility** | Visible to all code in the box | Never enters the container |
| **Use case** | Non-sensitive config, or when you trust all code in the box | API keys, tokens, credentials for untrusted code |
| **SDK compatibility** | Works with any SDK that reads from env | Works with any SDK that makes HTTPS requests |
| **Setup** | Pass in `env` | Pass in `attachHeaders` with host patterns |

## Limitations

* Attach headers are set at box creation time after merging global and per-box values
* They are not updated on a running box
* Only HTTPS traffic on port `443` is intercepted
* HTTP/2 connections through matched hosts are downgraded to HTTP/1.1
* Header values are encrypted at rest and never returned by API responses

# AI Actions
Source: https://upstash.com/docs/box/overall/browser/ai-actions

Beyond reading pages, a tab can act. A DOM-aware browser agent runs inside the box and resolves natural-language instructions against the live page. It can find elements, execute single actions, or complete multi-step tasks on its own.

<Note>
  AI actions use an LLM and are metered. They need an API key for the model's
  provider (Anthropic, OpenAI, OpenRouter, Vercel, or OpenCode) on the box or
  your account. Every method accepts a provider-prefixed `model` override such
  as `"openai/gpt-4o"`. Without an override, the call uses the model the box
  was configured with. If the box has no model, it falls back to
  `anthropic/claude-sonnet-4-5`.
</Note>

## Observe

`observe()` finds actionable elements matching an instruction. Use it to check the page before acting, or to build your own action loop:

<CodeGroup>
```typescript box.ts
const { elements } = await tab.observe("find the login and signup buttons")

for (const el of elements) {
  console.log(el.description, el.selector)
}
```

```python box.py
result = tab.observe("find the login and signup buttons")

for el in result.elements:
    print(el.description, el.selector)
```
</CodeGroup>

## Act

`act()` resolves and executes exactly one action described in natural language:

<CodeGroup>
```typescript box.ts
const action = await tab.act("click the primary call-to-action")

console.log(action.success, action.actionDescription)
console.log(action.inputTokens, action.outputTokens)
```

```python box.py
action = tab.act("click the primary call-to-action")

print(action.success, action.action_description)
print(action.input_tokens, action.output_tokens)
```
</CodeGroup>

The result reports what was done (`actions` with the resolved selectors), whether it succeeded, and the token usage of the call.

## Run

`run()` is the autonomous mode. The agent reads the page, acts, and repeats until the task is complete or it hits the step limit. Pass a schema to get structured data back at the end:

<CodeGroup>
```typescript box.ts
import { z } from "zod"

const { data, completed, steps } = await tab.run(
  "Find the pricing page and summarize the free tier",
  {
    schema: z.object({ summary: z.string() }),
    maxSteps: 15,
    // model: "openai/gpt-4o", // any provider you hold a key for
  },
)

console.log(completed, data.summary)
for (const step of steps) {
  console.log(step.step, step.action, step.url)
}
```

```python box.py
from pydantic import BaseModel

class Summary(BaseModel):
    summary: str

result = tab.run(
    "Find the pricing page and summarize the free tier",
    schema=Summary,
    max_steps=15,
    # model="openai/gpt-4o",  # any provider you hold a key for
)

print(result.completed, result.data.summary)
for step in result.steps:
    print(step.step, step.action, step.url)
```
</CodeGroup>

* `maxSteps`: defaults to `15`, capped at `30`.
* `schema`: optional. Without it, `run` returns its findings as text in `result`.
* The result includes `completed`, a step-by-step trace in `steps` (each with the action taken, its reasoning, and the URL), and total token usage.

## Which one to use

| Method | Does | Best for |
|---|---|---|
| `observe` | Finds elements, executes nothing | Inspecting a page, building custom loops |
| `act` | Executes one action | Flows where your code decides each step |
| `run` | Executes a whole task autonomously | Open-ended or navigation-heavy tasks |

For fully scripted control with no LLM in the loop, [connect over CDP](/docs/box/overall/browser/connect) with Playwright or Puppeteer instead. Both drive the same tabs, so you can mix scripted steps with AI steps. To watch or replay what the agent did, see [Live View](/docs/box/overall/browser/live-view) and [Recordings](/docs/box/overall/browser/recordings).

# Connect over CDP
Source: https://upstash.com/docs/box/overall/browser/connect

The box browser is a real Chromium, and you can drive it with the tools you already use. `cdpUrl()` returns an authenticated Chrome DevTools Protocol WebSocket URL that Playwright, Puppeteer, or Stagehand can connect to directly. There is no browser to install and nothing to manage.

<CodeGroup>
```typescript box.ts
const cdpUrl = await box.browser.cdpUrl()
// wss://…?token=…
```

```python box.py
cdp_url = box.browser.cdp_url()
# wss://…?token=…
```
</CodeGroup>

Like [live view](/docs/box/overall/browser/live-view) URLs, the CDP URL carries its auth token in the URL. Anyone who has it gets full control of the browser, so treat it as a secret.

## Playwright

`playwright-core` is enough here, since you connect to the box's Chromium instead of launching one locally:

<CodeGroup>
```typescript scrape.ts
import { chromium } from "playwright-core"

const browser = await chromium.connectOverCDP(cdpUrl)

const context = browser.contexts()[0] ?? (await browser.newContext())
const page = context.pages()[0] ?? (await context.newPage())

await page.goto("https://upstash.com")
console.log(await page.title())
```

```python scrape.py
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(cdp_url)
    context = browser.contexts[0] if browser.contexts else browser.new_context()
    page = context.pages[0] if context.pages else context.new_page()
    page.goto("https://upstash.com")
    print(page.title())
```
</CodeGroup>

## Puppeteer

```typescript scrape.ts
import puppeteer from "puppeteer-core"

const browser = await puppeteer.connect({ browserWSEndpoint: cdpUrl })

const page = (await browser.pages())[0] ?? (await browser.newPage())
await page.goto("https://upstash.com")
```

## Stagehand

[Stagehand](https://www.stagehand.dev) can use the box browser as its local browser:

```typescript agent.ts
import { Stagehand } from "@browserbasehq/stagehand"

const stagehand = new Stagehand({
  env: "LOCAL",
  localBrowserLaunchOptions: { cdpUrl },
})

await stagehand.init()
await stagehand.act("click the first link")
```

## Mixing CDP and SDK control

CDP clients and the SDK drive the same browser and the same tabs. A page opened by Playwright shows up in `box.browser.listTabs()`, and a tab created by the SDK is visible to Playwright. You can script the predictable steps like login and pagination with Playwright, hand the tab to [`act` or `run`](/docs/box/overall/browser/ai-actions) for the steps that are easier to describe in natural language, and watch either through [Live View](/docs/box/overall/browser/live-view).

As a rule of thumb: use CDP when you want precise, repeatable scripting with no LLM in the loop. Use [AI Actions](/docs/box/overall/browser/ai-actions) when describing the task is easier than scripting it.

# Cookbook
Source: https://upstash.com/docs/box/overall/browser/cookbook

<Info>
  Every example is a single self-contained file in the [Box SDK
  repo](https://github.com/upstash/box/tree/main/packages/sdk/examples/browser).
  All of them need `UPSTASH_BOX_API_KEY`. Examples marked **AI** use metered
  browser AI (`run`, `act`, `extract`) and need a model provider key on the box
  or account. Everything else runs with the Box key alone.
</Info>

## Agents

Goal-driven browsing with `tab.run()`.

<CardGroup cols={2}>
  <Card title="Search with fallback" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/agents/01-search-with-fallback.ts">
    Constrained search with a fallback category. The agent evaluates results, rejects with reasons, and switches on its own. **AI**
  </Card>

  <Card title="Playwright vs act vs run" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/agents/02-playwright-vs-act-vs-run.ts">
    The same task via Playwright, `act` plus `extract`, and `run`. Pick your autonomy level by token cost. **AI**
  </Card>

  <Card title="Observe, record, audit" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/agents/03-observe-record-audit.ts">
    The search again with live view, session recording, a decision log, and token accounting. **AI**
  </Card>

  <Card title="Multisite feed" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/agents/04-multisite-feed.ts">
    One prompt and one schema across three differently structured sites. **AI**
  </Card>
</CardGroup>

## Automation

Forms, files, and durable sessions.

<CardGroup cols={2}>
  <Card title="Checkout with one AI step" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/automation/01-checkout-with-one-ai-step.ts">
    Script the deterministic steps, delegate the judgment call to a single `act`, and assert the result.
  </Card>

  <Card title="Download and process" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/automation/02-download-and-process.ts">
    A download lands on the box filesystem and is processed in place.
  </Card>

  <Card title="Upload from box" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/automation/03-upload-from-box.ts">
    Upload a box-generated file through a real form over CDP.
  </Card>

  <Card title="Log in once, reuse session" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/automation/04a-login-once-keep-alive.ts">
    Log in once into a keep-alive box, then have any later script reuse the session.
  </Card>
</CardGroup>

## Retrieval

Structured data out of rendered pages.

<CardGroup cols={2}>
  <Card title="Catalog extraction" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/retrieval/01-catalog-extraction.ts">
    One `extract` call, schema-validated typed output, screenshot provenance. **AI**
  </Card>

  <Card title="AI-compiled scraper" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/retrieval/02-ai-compiled-scraper.ts">
    AI compiles selectors once, then every later run scrapes deterministically. **AI (first run only)**
  </Card>

  <Card title="Crawl to dataset" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/retrieval/03-crawl-to-dataset.ts">
    Crawl a client-rendered docs section into chunked JSONL via `tab.content()`.
  </Card>

  <Card title="Evidence pack" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/retrieval/04-evidence-pack.ts">
    Full-page captures plus a hashed manifest, assembled in the box and downloaded as one archive.
  </Card>
</CardGroup>

## Testing

The box as a test environment.

<CardGroup cols={2}>
  <Card title="Playwright migration" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/testing/01-playwright-migration.ts">
    An existing Playwright test where only the launch line changes.
  </Card>

  <Card title="Test your own app" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/testing/02-test-your-own-app.ts">
    The box hosts the app under test and browses it on its own localhost.
  </Card>

  <Card title="AI smoke tests" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/testing/03-ai-smoke-tests.ts">
    Agent-driven smoke flow, cross-checked by deterministic DOM assertions, recorded on video. **AI**
  </Card>

  <Card title="Visual regression" href="https://github.com/upstash/box/blob/main/packages/sdk/examples/browser/testing/04-visual-regression.ts">
    Pixelmatch diffs against baselines stored on the box.
  </Card>
</CardGroup>

# Live View
Source: https://upstash.com/docs/box/overall/browser/live-view

Every tab can produce a live view URL: a shareable link that streams the tab in real time. Open it in any browser, or embed it in an `<iframe>` to show your users what their agent is doing.

<CodeGroup>
```typescript box.ts
const liveUrl = await tab.liveViewUrl()

console.log(liveUrl)
// https://<box>-9223.<domain>/screencast?token=…&tab=…
```

```python box.py
live_url = tab.live_view_url()

print(live_url)
# https://<box>-9223.<domain>/screencast?token=…&tab=…
```
</CodeGroup>

The URL is self-contained. Authentication is a token embedded in the URL itself, so it works anywhere a browser can load a page. The viewing side needs no API key and no SDK.

## Embedding

```html
<iframe
  src="https://<box>-9223.<domain>/screencast?token=…&tab=…"
  style="width: 100%; aspect-ratio: 16 / 10; border: 0;"
></iframe>
```

A common pattern is to start a [`tab.run()`](/docs/box/overall/browser/ai-actions) task and render the live view next to it, so users can watch the agent work in real time.

## View-only

The live view streams frames out only. Viewers cannot click, type, or otherwise interact with the page. To drive the browser, use the SDK or [connect over CDP](/docs/box/overall/browser/connect).

If you need an interactive viewer inside your own product, you can build one over CDP by streaming screencast frames out and sending input events back. This is how the console's browser tab works.

<Warning>
  Anyone with the URL can watch the tab, since the access token is part of the
  URL. Treat live view URLs like secrets and share them only with people who
  should see the session.
</Warning>

For an interactive version of the same view, open the **Browser** tab on your box's page in the [Upstash Console](https://console.upstash.com). If you want a replayable video instead of a live stream, see [Recordings](/docs/box/overall/browser/recordings).

# Browser
Source: https://upstash.com/docs/box/overall/browser/overview

**Every box can come with its own browser.** Create a box with `browser: true` to get a managed, headless Chromium that you control through the SDK. You can open tabs, read pages, take screenshots, extract structured data, run AI agents on the live DOM, record sessions, and connect Playwright directly over CDP.

Everything works headless. There is no desktop, no VNC, and nothing to install. Chromium is provisioned with the box and boots on first use.

## Create a box with a browser

<CodeGroup>
```typescript box.ts
import { Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  browser: true,
})

// Open a tab (boots Chromium on first use)
const tab = await box.browser.tab.create("https://news.ycombinator.com")

// Navigate the same tab and read the result
const page = await tab.goto("https://upstash.com/docs")
console.log(page.title)
```

```python box.py
from upstash_box import Box

box = Box.create(runtime="node", browser=True)

# Open a tab (boots Chromium on first use)
tab = box.browser.tab.create("https://news.ycombinator.com")

# Navigate the same tab and read the result
page = tab.goto("https://upstash.com/docs")
print(page.title)
```
</CodeGroup>

<Note>
  The browser can only be provisioned when the box is created. It cannot be
  enabled on an existing box. If you need a browser on a box that does not have
  one, create a new box with `browser: true`.
</Note>

## How it's organized

`box.browser` manages the browser itself: opening and listing tabs, recordings, and the CDP endpoint. Page-level operations live on a **Tab** handle, the object returned by `tab.create`, `listTabs`, or `getTab`. A tab handle is addressed by its Chrome DevTools Protocol target id, so it stays valid across navigations. You navigate, read, screenshot, and run AI tasks through the same handle.

## What you can do

<CardGroup cols={2}>
  <Card title="Tabs & Navigation" href="/box/overall/browser/tabs">
    Open tabs, navigate, list and re-attach to them, and close them.
  </Card>

  <Card title="Reading Pages" href="/box/overall/browser/reading-pages">
    Page text and links, PNG screenshots, and schema-validated data extraction.
  </Card>

  <Card title="AI Actions" href="/box/overall/browser/ai-actions">
    Natural-language actions and autonomous multi-step tasks on the live DOM.
  </Card>

  <Card title="Live View" href="/box/overall/browser/live-view">
    A shareable, view-only live stream of any tab that you can embed in an iframe.
  </Card>

  <Card title="Recordings" href="/box/overall/browser/recordings">
    Record browser sessions to replayable video with chapter markers.
  </Card>

  <Card title="Connect over CDP" href="/box/overall/browser/connect">
    Drive the same browser with Playwright, Puppeteer, or Stagehand.
  </Card>
</CardGroup>

<Note>
  The AI-powered operations use an LLM and are metered:
  [`extract`](/docs/box/overall/browser/reading-pages) and [`observe`, `act`,
  `run`](/docs/box/overall/browser/ai-actions). They need an API key for the model's
  provider (Anthropic, OpenAI, OpenRouter, Vercel, or OpenCode) on the box or
  your account.
</Note>

You can also watch and control the browser from the **Browser** tab on your box's page in the [Upstash Console](https://console.upstash.com). It shows the live view, runs AI tasks, and includes the SDK snippet for everything you do there.

# Reading Pages
Source: https://upstash.com/docs/box/overall/browser/reading-pages

There are three ways to get data out of a tab: read the DOM as text, capture a screenshot, or have an AI agent extract structured data against a schema.

## Page content

`content()` reads the tab's current title, URL, visible text, and links from the real DOM, including JavaScript-rendered content:

<CodeGroup>
```typescript box.ts
const { title, url, text, links } = await tab.content()

console.log(title)
console.log(text.slice(0, 200))
for (const link of links ?? []) {
  console.log(link.text, link.href)
}
```

```python box.py
content = tab.content()

print(content.title)
print(content.text[:200])
for link in content.links or []:
    print(link.text, link.href)
```
</CodeGroup>

## Screenshots

`screenshot()` captures the tab as a PNG. It works headless, with no display needed. By default you get raw PNG bytes. Ask for base64 if you are passing the image on, for example to an LLM:

<CodeGroup>
```typescript box.ts
import { writeFile } from "node:fs/promises"

// PNG bytes (Uint8Array)
const png = await tab.screenshot({ fullPage: true })
await writeFile("page.png", png)

// Base64-encoded PNG string
const b64 = await tab.screenshot({ type: "base64" })
```

```python box.py
# PNG bytes
png = tab.screenshot(full_page=True)
with open("page.png", "wb") as f:
    f.write(png)

# Base64-encoded PNG string
b64 = tab.screenshot(encoding="base64")
```
</CodeGroup>

Pass `fullPage: true` to capture the entire scrollable page instead of just the viewport.

## Structured extraction

`extract()` hands the page to a DOM-aware AI agent and returns data validated against your schema. The schema is a [Zod](https://zod.dev) object schema (v3 or v4) in TypeScript, and a [pydantic](https://docs.pydantic.dev) model class or a raw JSON schema dict in Python:

<CodeGroup>
```typescript box.ts
import { z } from "zod"

const article = await tab.extract(
  "extract the article title and author",
  z.object({
    title: z.string(),
    author: z.string(),
  }),
)

console.log(article.title, article.author)
```

```python box.py
from pydantic import BaseModel

class Article(BaseModel):
    title: str
    author: str

article = tab.extract(
    "extract the article title and author",
    Article,
)

print(article.title, article.author)
```
</CodeGroup>

<Note>
  `extract` uses an LLM and is metered. It needs a provider API key on the box
  or your account, and accepts an optional `model` override just like the other
  [AI Actions](/docs/box/overall/browser/ai-actions).
</Note>

The result is parsed with your schema before it is returned, so a successful call always gives you data in the shape you asked for.

# Recordings
Source: https://upstash.com/docs/box/overall/browser/recordings

Recordings capture the browser to a replayable video. They cover all tabs, follow whichever one is in the foreground, and include everything an [AI task](/docs/box/overall/browser/ai-actions) does. Use them for audit trails, debugging agent behavior, or showing users what happened after the fact.

## Record a session

<CodeGroup>
```typescript box.ts
// Start capturing (one active recording per box)
const recording = await box.browser.recordings.start({
  maxDurationSeconds: 120,
})

await tab.goto("https://upstash.com/docs")
await tab.run("Find the quickstart and summarize it")

// Finalize the video and upload it
const saved = await recording.stop()

console.log(saved.durationMs, saved.playlistUrl)
console.log(saved.markers) // tab switches and AI run chapters
```

```python box.py
# Start capturing (one active recording per box)
recording = box.browser.recordings.start(max_duration_seconds=120)

tab.goto("https://upstash.com/docs")
tab.run("Find the quickstart and summarize it")

# Finalize the video and upload it
saved = recording.stop()

print(saved.duration_ms, saved.playlist_url)
print(saved.markers)  # tab switches and AI run chapters
```
</CodeGroup>

A recording stops when you call `stop()`, when it reaches `maxDurationSeconds` (default and maximum: 600 seconds), or automatically after 3 minutes with no on-screen activity.

## Playback

A completed recording is an HLS video. `playlistUrl` points to its playlist, and `markers` holds chapters for tab switches (`tab_switch`) and AI runs (`run`) with their timestamps. A player can use the markers to jump straight to a specific run.

<Note>
  Unlike [live view](/docs/box/overall/browser/live-view) URLs, the playlist URL is
  not tokenized. Fetching it requires your Box API key, like any other API
  call. Recordings are retained for 14 days.
</Note>

The easiest way to watch a recording is the **Browser** tab of your box in the [Upstash Console](https://console.upstash.com). To play recordings in your own product, keep the API key on your server: proxy the playlist and segment requests through your backend, attach the `X-Box-Api-Key` header there, and feed the proxied playlist to an HLS player such as [hls.js](https://github.com/video-dev/hls.js). Do not ship the key to end users.

## Download

Save a completed recording as a regular MP4 file:

<CodeGroup>
```typescript box.ts
const path = await box.browser.recordings.download(saved.id, {
  path: "./session.mp4",
})
```

```python box.py
path = box.browser.recordings.download(saved.id, path="./session.mp4")
```
</CodeGroup>

When `path` is omitted, the file is written to the working directory as `box-recording-<id>.mp4`. Recordings captured before MP4 support download as raw MPEG-TS with a `.ts` extension instead; check `mp4SizeBytes` (`mp4_size_bytes` in Python) on the recording's metadata if you need to know in advance.

## Find recordings later

Recordings belong to the box and can be discovered again after a restart or from another process:

<CodeGroup>
```typescript box.ts
// Newest first
const recordings = await box.browser.recordings.list()

const same = await box.browser.recordings.get(recordings[0].id)
console.log(same.status, same.durationMs, same.sizeBytes)
```

```python box.py
# Newest first
recordings = box.browser.recordings.list()

same = box.browser.recordings.get(recordings[0].id)
print(same.status, same.duration_ms, same.size_bytes)
```
</CodeGroup>

Each recording reports its `status` (`recording`, `completed`, `failed`, or `deleted`), timing (`startedAt`, `endedAt`, `durationMs`), size, why it stopped (`stoppedReason`), and its expiry time.

<Tip>
  `recording.stop()` is safe to call on a stale handle. If that recording
  already ended, for example because it auto-stopped and a newer one is
  running, it returns the finished recording's metadata instead of stopping
  the newer one.
</Tip>

# Tabs & Navigation
Source: https://upstash.com/docs/box/overall/browser/tabs

Tabs are the unit of work in the box browser. `box.browser` opens and lists them. Every page operation, from navigation to screenshots to AI actions, runs on a specific `Tab` handle.

## Open a tab

`tab.create` opens a tab, navigates it to a URL, and waits for the requested lifecycle state. The first call also boots Chromium if it is not running yet.

<CodeGroup>
```typescript box.ts
const tab = await box.browser.tab.create("https://upstash.com", {
  waitUntil: "domcontentloaded",
  timeout: 30_000,
})

console.log(tab.id, tab.url, tab.title)
```

```python box.py
tab = box.browser.tab.create(
    "https://upstash.com",
    wait_until="domcontentloaded",
    timeout=30_000,
)

print(tab.id, tab.url, tab.title)
```
</CodeGroup>

* `waitUntil`: when navigation counts as done. One of `"load"` (default), `"domcontentloaded"`, or `"networkidle"`.
* `timeout`: navigation timeout in milliseconds. Defaults to `30000`. Pass `0` to disable it.

## Navigate

`goto` navigates the tab and returns the resulting page's content (title, URL, text, and links):

<CodeGroup>
```typescript box.ts
const page = await tab.goto("https://upstash.com/docs")
console.log(page.title, page.url)
```

```python box.py
page = tab.goto("https://upstash.com/docs")
print(page.title, page.url)
```
</CodeGroup>

Unlike `tab.create`, `goto` has no `waitUntil` or `timeout` options. It waits for the page to load with a fixed 60 second deadline.

## List and re-attach

Tab handles are addressed by their Chrome DevTools Protocol target id, which stays stable across navigations. You can store a tab id and re-attach to the same tab later, even from a different process:

<CodeGroup>
```typescript box.ts
// List the box's open tabs
const tabs = await box.browser.listTabs()
for (const t of tabs) {
  console.log(t.id, t.url, t.title)
}

// Re-attach to a tab by id (no network call)
const same = box.browser.getTab(tab.id)
await same.goto("https://news.ycombinator.com")
```

```python box.py
# List the box's open tabs
tabs = box.browser.list_tabs()
for t in tabs:
    print(t.id, t.url, t.title)

# Re-attach to a tab by id (no network call)
same = box.browser.get_tab(tab.id)
same.goto("https://news.ycombinator.com")
```
</CodeGroup>

<Note>
  A handle's `url` and `title` fields are the last known values from
  `tab.create` or `listTabs`. They are not updated live. Use
  [`tab.content()`](/docs/box/overall/browser/reading-pages) to read the current
  state of the page.
</Note>

## Close a tab

<CodeGroup>
```typescript box.ts
await tab.close()
```

```python box.py
tab.close()
```
</CodeGroup>

Multiple tabs can be open at once. Each is independent, and operations on one do not affect the others. This is useful for comparing pages side by side or running [AI tasks](/docs/box/overall/browser/ai-actions) against several pages in sequence.

# How to Add a Custom Agent
Source: https://upstash.com/docs/box/overall/custom-agent

Custom agents let you bring your own agent process to an Upstash Box. The box still provides the same sandbox, filesystem, shell, git, logs, streaming, and console experience, but your code decides how to call the model and how to produce output.

Use a custom agent when the built-in Claude Code, Codex, OpenCode, or Cursor agents do not fit your workflow.

## Create a Custom Agent Box

Create the box with `agent.harness: Agent.Custom` and provide a `customHarness` command. The command runs inside the box for every `box.agent.run()` or `box.agent.stream()` call.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  apiKey: process.env.UPSTASH_BOX_API_KEY!,
  runtime: "node",
  agent: {
    harness: Agent.Custom,
    model: "claude-haiku-4-5-20251001",
    customHarness: {
      command: "node",
      args: ["/workspace/home/custom-anthropic-agent.mjs"],
      protocol: "box-sse-v1",
    },
  },
  env: {
    ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY!,
  },
})
```

```python box.py
import os
from upstash_box import Agent, Box

box = Box.create(
    api_key=os.environ["UPSTASH_BOX_API_KEY"],
    runtime="node",
    agent={
        "harness": Agent.CUSTOM,
        "model": "claude-haiku-4-5-20251001",
        "custom_harness": {
            "command": "node",
            "args": ["/workspace/home/custom-anthropic-agent.mjs"],
            "protocol": "box-sse-v1",
        },
    },
    env={
        "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"],
    },
)
```
</CodeGroup>

## Agent Contract

For each run, Box executes your command and appends these arguments:

```bash
-p "<prompt>" --model "<model>" --stream --session "<session-id>"
```

`--session` is only included when a prior session exists.

Your process must write Server-Sent Events to `stdout` using the `box-sse-v1` protocol:

```txt
event: text
data: {"text":"Hello"}

event: done
data: {"output":"Hello","input_tokens":10,"output_tokens":4,"total_cost_usd":0.0001,"session_id":"session-1"}
```

Supported event names:

| Event | Description |
| --- | --- |
| `text` | Adds text to the visible response |
| `thinking` | Emits reasoning/thinking text |
| `tool` | Shows a tool call in logs |
| `tool_result` | Shows a tool result in logs |
| `done` | Finishes the run successfully |
| `error` | Finishes the run with an error |

## SDK Helper

If your custom agent process can import `@upstash/box`, use `runCustomHarness()` to parse Box arguments and emit the protocol events:

<CodeGroup>
```typescript box.ts
import { runCustomHarness } from "@upstash/box"

await runCustomHarness(async ({ prompt, model, sessionId }, emit) => {
  emit.tool({ name: "my_agent", input: { model } })

  const output = `received: ${prompt}`
  emit.text(output)

  return {
    output,
    inputTokens: prompt.split(/\s+/).length,
    outputTokens: output.split(/\s+/).length,
    sessionId,
  }
})
```

```python box.py
import asyncio
from upstash_box import run_custom_harness, CustomHarnessDone

async def handler(ctx, emit):
    emit.tool({"name": "my_agent", "input": {"model": ctx.model}})

    output = f"received: {ctx.prompt}"
    emit.text(output)

    return CustomHarnessDone(
        output=output,
        input_tokens=len(ctx.prompt.split()),
        output_tokens=len(output.split()),
        session_id=ctx.session_id,
    )

asyncio.run(run_custom_harness(handler))
```
</CodeGroup>

## Minimal Anthropic Agent

This custom agent calls Anthropic directly and streams text back through Box.

```js title="custom-anthropic-agent.mjs"
const args = process.argv.slice(2)

function readArg(name, fallback = "") {
  const index = args.indexOf(name)
  return index >= 0 ? args[index + 1] ?? fallback : fallback
}

function emit(event, data) {
  process.stdout.write(`event: ${event}\n`)
  process.stdout.write(`data: ${JSON.stringify(data)}\n\n`)
}

const prompt = readArg("-p")
const model = readArg("--model", "claude-haiku-4-5-20251001")
const sessionId = readArg("--session") || crypto.randomUUID()

try {
  emit("tool", {
    name: "anthropic_messages",
    input: { model },
  })

  const response = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-api-key": process.env.ANTHROPIC_API_KEY,
      "anthropic-version": "2023-06-01",
    },
    body: JSON.stringify({
      model,
      max_tokens: 1024,
      messages: [{ role: "user", content: prompt }],
    }),
  })

  const body = await response.json()

  if (!response.ok) {
    throw new Error(body.error?.message ?? `Anthropic request failed: ${response.status}`)
  }

  const output = body.content
    ?.filter((part) => part.type === "text")
    .map((part) => part.text)
    .join("") ?? ""

  emit("text", { text: output })
  emit("done", {
    output,
    input_tokens: body.usage?.input_tokens ?? 0,
    output_tokens: body.usage?.output_tokens ?? 0,
    session_id: sessionId,
  })
} catch (error) {
  emit("error", {
    error: error instanceof Error ? error.message : String(error),
    session_id: sessionId,
  })
  process.exitCode = 1
}
```

Write the custom agent into the box before the first run:

<CodeGroup>
```typescript box.ts
await box.files.write({
  path: "custom-anthropic-agent.mjs",
  content: agentSource,
})

const result = await box.agent.run({
  prompt: "Say hello from my custom agent",
})

console.log(result.result)
```

```python box.py
box.files.write(
    path="custom-anthropic-agent.mjs",
    content=agent_source,
)

result = box.agent.run(prompt="Say hello from my custom agent")

print(result.result)
```
</CodeGroup>

## Update an Existing Custom Agent

You can update the custom agent command for an existing custom box:

<CodeGroup>
```typescript box.ts
await box.configureCustomHarness({
  command: "node",
  args: ["/workspace/home/another-agent.mjs"],
  protocol: "box-sse-v1",
})
```

```python box.py
box.configure_custom_harness({
    "command": "node",
    "args": ["/workspace/home/another-agent.mjs"],
    "protocol": "box-sse-v1",
})
```
</CodeGroup>

This only works for boxes created with `agent.harness: Agent.Custom`.

## Custom Harness Examples

Ready-to-run examples for popular open-source agents:

* [Pi](https://github.com/upstash/box/blob/main/packages/sdk/examples/custom-pi-agent.ts) — multi-provider coding agent
* [Gemini](https://github.com/upstash/box/blob/main/packages/sdk/examples/custom-gemini-agent.ts) — Google Gemini via `@google/genai`
* [Aider](https://github.com/upstash/box/blob/main/packages/sdk/examples/custom-aider-agent.ts) — git-aware code editor
* [Goose](https://github.com/upstash/box/blob/main/packages/sdk/examples/custom-goose-agent.ts) — autonomous coding agent
* [CrewAI](https://github.com/upstash/box/blob/main/packages/python-sdk/examples/custom_crewai_agent.py) — multi-agent orchestration framework
* [Pydantic AI](https://github.com/upstash/box/blob/main/packages/python-sdk/examples/custom_pydantic_ai_agent.py) — type-safe agent framework

## Notes

* Custom agents do not use managed provider keys.
* Pass secrets through `env` on `Box.create()` or configure them inside the box.
* The command must be a binary name from `PATH` or an absolute path under `/workspace/home/` or `/home/boxuser/`.
* The command runs as `boxuser` inside the existing box sandbox.

# Aider
Source: https://upstash.com/docs/box/overall/custom-harness/aider

# CrewAI
Source: https://upstash.com/docs/box/overall/custom-harness/crewai

# Gemini
Source: https://upstash.com/docs/box/overall/custom-harness/gemini

# Goose
Source: https://upstash.com/docs/box/overall/custom-harness/goose

# Pi
Source: https://upstash.com/docs/box/overall/custom-harness/pi

# Pydantic AI
Source: https://upstash.com/docs/box/overall/custom-harness/pydantic-ai

# Ephemeral Box
Source: https://upstash.com/docs/box/overall/ephemeral-box

`EphemeralBox` is a lightweight, short-lived sandbox that provides only **exec** and **file** operations. It's designed for quick, disposable compute tasks where a full Box (with agent, git, snapshots, etc.) is unnecessary.

***

## Creation

<CodeGroup>
```typescript box.ts
import { EphemeralBox } from "@upstash/box"

const box = await EphemeralBox.create({
  runtime: "node",  // "node" | "python" | "golang" | "ruby" | "rust"
  ttl: 3600,        // seconds, max 259200 (3 days), default 259200
  name: "my-ephemeral-worker",          // optional
  networkPolicy: { mode: "allow-all" }, // optional
})
```

```python box.py
from upstash_box import EphemeralBox

box = EphemeralBox.create(
    runtime="node",  # "node" | "python" | "golang" | "ruby" | "rust"
    ttl=3600,        # seconds, max 259200 (3 days), default 259200
    name="my-ephemeral-worker",            # optional
    network_policy={"mode": "allow-all"},  # optional
)
```
</CodeGroup>

**Key difference from `Box.create()`:** Ephemeral boxes are ready immediately — no polling. The API returns with `status: "idle"` and the box is usable right away.

The request sends `{ ephemeral: true, ttl?, runtime? }` to `POST /v2/box`.

***

## Available API

| Feature | Box | EphemeralBox |
|---|---|---|
| `exec.command()` | Yes | Yes |
| `exec.code()` | Yes | Yes |
| `exec.stream()` | Yes | Yes |
| `exec.streamCode()` | Yes | Yes |
| `files.read/write/list/upload/download` | Yes | Yes |
| `schedule.exec/prompt/list/get/pause/resume/delete` | Yes | Yes |
| `cd()` / `cwd` | Yes | Yes |
| `getStatus()` | Yes | Yes |
| `delete()` | Yes | Yes |
| `networkPolicy` / `updateNetworkPolicy()` | Yes | Yes |
| `expiresAt` | No | Yes |
| `agent.run()` / `agent.stream()` | Yes | **No** |
| `git.*` | Yes | **No** |
| `getPublicURL()` / `listPublicURLs()` / `deletePublicURL()` | Yes | **No** |
| `snapshot()` / `fromSnapshot()` | Yes | Yes |
| `pause()` / `resume()` | Yes | **No** |
| `configureModel()` | Yes | **No** |
| `logs()` / `listRuns()` | Yes | **No** |

***

## Properties

* **`id`** — box identifier (e.g. `"sweet-shark-26021"`)
* **`expiresAt`** — Unix timestamp (seconds) when the box auto-deletes

***

## How it differs from Box

1. **Instant creation** — no polling loop; the response is the ready box
2. **Auto-expiry** — boxes are automatically deleted after TTL; `expiresAt` tracks this
3. **Reduced surface** — only exec + files; no agent, git, public URLs, snapshots, pause/resume
4. **Simpler config** — `EphemeralBoxConfig` has only `apiKey`, `runtime`, `ttl`, `name`, `networkPolicy`, `baseUrl`, `timeout`, `debug` (no agent, git, env, skills, mcpServers)
5. **Composition over inheritance** — `EphemeralBox` wraps an internal `Box` and exposes only the relevant subset, so agent/git/etc. are not accessible even at runtime

***

## Examples

### Run a shell command

<CodeGroup>
```typescript box.ts
const run = await box.exec.command("echo hello")
console.log(run.result) // "hello"
```

```python box.py
run = box.exec.command("echo hello")
print(run.result)  # "hello"
```
</CodeGroup>

### Execute inline code

<CodeGroup>
```typescript box.ts
const result = await box.exec.code({
  code: 'console.log(JSON.stringify({ sum: 1 + 2 }))',
  lang: "js",
})
```

```python box.py
result = box.exec.code(
    code="import json; print(json.dumps({'sum': 1 + 2}))",
    lang="python",
)
```
</CodeGroup>

### File operations

<CodeGroup>
```typescript box.ts
await box.files.write({ path: "data.json", content: '{"key":"value"}' })
const content = await box.files.read("data.json")
```

```python box.py
box.files.write(path="data.json", content='{"key":"value"}')
content = box.files.read("data.json")
```
</CodeGroup>

### Clean up early

Otherwise the box auto-deletes at `expiresAt`.

<CodeGroup>
```typescript box.ts
await box.delete()
```

```python box.py
box.delete()
```
</CodeGroup>

***

## Exported types

* `EphemeralBox` — the class
* `EphemeralBoxConfig` — config for `EphemeralBox.create()`
* `EphemeralBoxData` — extends `BoxData` with `ephemeral: boolean` and `expires_at: number`

# Filesystem
Source: https://upstash.com/docs/box/overall/files

Every box has its own isolated filesystem. Upload, write, read, list, and download files inside the box.

***

## API

### Upload files

Push local files into the box. Each entry maps a local path to a destination inside the box workspace.

<CodeGroup>
```typescript box.ts
await box.files.upload([
  { path: "./data/report.csv", destination: "/work/report.csv" },
  { path: "./config.json", destination: "/work/config.json" },
])
```

```python box.py
box.files.upload([
    {"path": "./data/report.csv", "destination": "/work/report.csv"},
    {"path": "./config.json", "destination": "/work/config.json"},
])
```
</CodeGroup>

You can upload multiple files in a single call. All uploads run in parallel.

***

### Write files

Create or overwrite a file directly from a string. Useful when you want to inject configuration, scripts, or generated content without a local file.

<CodeGroup>
```typescript box.ts
await box.files.write({
  path: "/work/script.js",
  content: `console.log("hello from box")`,
})
```

```python box.py
box.files.write(
    path="/work/script.js",
    content='console.log("hello from box")',
)
```
</CodeGroup>

***

### Read files

Read the contents of a file as a string.

<CodeGroup>
```typescript box.ts
const content = await box.files.read("/work/output.json")
console.log(JSON.parse(content))
```

```python box.py
import json

content = box.files.read("/work/output.json")
print(json.loads(content))
```
</CodeGroup>

***

### List files

List the entries in a directory. Each entry includes the path, size, type, and last modified timestamp.

<CodeGroup>
```typescript box.ts
const files = await box.files.list("/work")

console.log(files)
// [
//   { path: "/work/report.csv", size: 1024, type: "file", modifiedAt: "2026-02-23T..." },
//   { path: "/work/output", size: 4096, type: "directory", modifiedAt: "2026-02-23T..." },
// ]
```

```python box.py
files = box.files.list("/work")

print(files)
# [
#   FileEntry(name="report.csv", path="/work/report.csv", size=1024, is_dir=False, mod_time="2026-02-23T..."),
#   FileEntry(name="output", path="/work/output", size=4096, is_dir=True, mod_time="2026-02-23T..."),
# ]
```
</CodeGroup>

***

### Download files

Pull files from the box back to your local machine. Call with no arguments to download the entire workspace, or pass a `folder` to download a specific file or directory.

<CodeGroup>
```typescript box.ts
await box.files.download()
await box.files.download({ folder: "/work/output" })
```

```python box.py
box.files.download()
box.files.download(folder="/work/output")
```
</CodeGroup>

***

## Examples

### Feed data to an agent

Upload input files, run the agent, then read back the structured result.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"
import { z } from "zod"

const box = await Box.create({
  runtime: "node",
  agent: { harness: Agent.ClaudeCode, model: "anthropic/claude-opus-4-6", apiKey: process.env.ANTHROPIC_API_KEY },
})

await box.files.upload([
  { path: "./resumes/candidate.pdf", destination: "/work/resume.pdf" },
])

const run = await box.agent.run({
  prompt: "Read /work/resume.pdf. Extract the candidate's name, email, and skills.",
  responseSchema: z.object({
    name: z.string(),
    email: z.string(),
    skills: z.array(z.string()),
  }),
})

console.log(run.result)
// { name: "Jane Doe", email: "jane@example.com", skills: ["TypeScript", "PostgreSQL"] }

await box.delete()
```

```python box.py
import os
from pydantic import BaseModel
from upstash_box import Box, Agent

class Candidate(BaseModel):
    name: str
    email: str
    skills: list[str]

box = Box.create(
    runtime="node",
    agent={"harness": Agent.CLAUDE_CODE, "model": "anthropic/claude-opus-4-6", "api_key": os.environ["ANTHROPIC_API_KEY"]},
)

box.files.upload([
    {"path": "./resumes/candidate.pdf", "destination": "/work/resume.pdf"},
])

run = box.agent.run(
    prompt="Read /work/resume.pdf. Extract the candidate's name, email, and skills.",
    response_schema=Candidate,
)

print(run.result)
# Candidate(name="Jane Doe", email="jane@example.com", skills=["TypeScript", "PostgreSQL"])

box.delete()
```
</CodeGroup>

### Inject config before a run

Write environment-specific configuration into the box, then let the agent use it.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  agent: { harness: Agent.ClaudeCode, model: "anthropic/claude-opus-4-6", apiKey: process.env.ANTHROPIC_API_KEY },
  git: { token: process.env.GITHUB_TOKEN },
})

await box.git.clone({ repo: "github.com/your-org/your-api" })

await box.files.write({
  path: "/work/your-api/.env.test",
  content: `DATABASE_URL=postgres://localhost:5432/test\nREDIS_URL=redis://localhost:6379`,
})

await box.agent.run({
  prompt: "Run the integration test suite using the config in .env.test",
})
```

```python box.py
import os
from upstash_box import Box, Agent

box = Box.create(
    runtime="node",
    agent={"harness": Agent.CLAUDE_CODE, "model": "anthropic/claude-opus-4-6", "api_key": os.environ["ANTHROPIC_API_KEY"]},
    git={"token": os.environ["GITHUB_TOKEN"]},
)

box.git.clone(repo="github.com/your-org/your-api")

box.files.write(
    path="/work/your-api/.env.test",
    content="DATABASE_URL=postgres://localhost:5432/test\nREDIS_URL=redis://localhost:6379",
)

box.agent.run(prompt="Run the integration test suite using the config in .env.test")
```
</CodeGroup>

### Collect outputs from parallel boxes

Fan out work across multiple boxes, then download each result locally.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const prompts = [
  "Analyze /work/data.csv and write a summary to /work/summary.md",
  "Generate charts from /work/data.csv and save PNGs to /work/charts/",
  "Find anomalies in /work/data.csv and write a report to /work/anomalies.md",
]

const results = await Promise.all(
  prompts.map(async (prompt) => {
    const box = await Box.create({
      runtime: "node",
      agent: { harness: Agent.ClaudeCode, model: "anthropic/claude-opus-4-6", apiKey: process.env.ANTHROPIC_API_KEY },
    })

    await box.files.upload([
      { path: "./data.csv", destination: "/work/data.csv" },
    ])

    await box.agent.run({ prompt })
    await box.files.download({ folder: "/work" })
    await box.delete()
  })
)
```

```python box.py
import asyncio
import os
from upstash_box import AsyncBox, Agent

prompts = [
    "Analyze /work/data.csv and write a summary to /work/summary.md",
    "Generate charts from /work/data.csv and save PNGs to /work/charts/",
    "Find anomalies in /work/data.csv and write a report to /work/anomalies.md",
]

async def run_one(prompt: str) -> None:
    box = await AsyncBox.create(
        runtime="node",
        agent={"harness": Agent.CLAUDE_CODE, "model": "anthropic/claude-opus-4-6", "api_key": os.environ["ANTHROPIC_API_KEY"]},
    )
    await box.files.upload([{"path": "./data.csv", "destination": "/work/data.csv"}])
    await box.agent.run(prompt=prompt)
    await box.files.download(folder="/work")
    await box.delete()

async def main() -> None:
    # fan out across boxes in parallel
    await asyncio.gather(*(run_one(p) for p in prompts))

asyncio.run(main())
```
</CodeGroup>

# Git
Source: https://upstash.com/docs/box/overall/git

Clone a repository, inspect changes, commit work, push a branch, or open a pull request from inside a box.

***

## Configure Git Access

If you want to work with private repositories, push changes, or create pull requests, create the box with a GitHub token.

<Note>
  For a fine-grained token, the following permissions are sufficient for basic usage:
  * **Contents** — Read and write
  * **Pull requests** — Read and write
</Note>

```bash title=".env" {2}
UPSTASH_BOX_API_KEY=box_xxxxxxxxxxxxxxxxxxxxxxxx
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxx
```

<CodeGroup>
```tsx box.ts {5-7}
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  git: {
    token: process.env.GITHUB_TOKEN,
  },
})
```

```python box.py
import os
from upstash_box import Box

box = Box.create(
    runtime="node",
    git={"token": os.environ["GITHUB_TOKEN"]},
)
```
</CodeGroup>

### Git identity

You can also set the git author identity that will be used for commits made inside the box. Both fields are optional and applied to the container's global git config on creation.

| Field       | Type     | Default              | Description                              |
| ----------- | -------- | -------------------- | ---------------------------------------- |
| `token`     | `string` | —                    | GitHub token for private repos and push  |
| `userName`  | `string` | `"Upstash Box"`      | Value written to `git config user.name`  |
| `userEmail` | `string` | `"box@upstash.com"`  | Value written to `git config user.email` |

<CodeGroup>
```tsx box.ts {5-9}
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  git: {
    token: process.env.GITHUB_TOKEN,
    userName: "deploy-bot",
    userEmail: "deploy-bot@acme.com",
  },
})
```

```python box.py
import os
from upstash_box import Box

box = Box.create(
    runtime="node",
    git={
        "token": os.environ["GITHUB_TOKEN"],
        "user_name": "deploy-bot",
        "user_email": "deploy-bot@acme.com",
    },
)
```
</CodeGroup>

If `userName` or `userEmail` are omitted, the box uses the defaults (`"Upstash Box"` / `"box@upstash.com"`). Omitting `token` is fine for public repositories where push access is not required.

***

## Quickstart

### Clone a repository

<CodeGroup>
```tsx box.ts
await box.git.clone({
  repo: "https://github.com/acme/web-app",
  branch: "main",
})

await box.cd("web-app")

await box.git.exec({
  args: ["checkout", "-b", "fix/empty-state"],
})
```

```python box.py
box.git.clone(repo="https://github.com/acme/web-app", branch="main")

box.cd("web-app")

box.git.exec(args=["checkout", "-b", "fix/empty-state"])
```
</CodeGroup>

### Inspect what changed

Use `status()` for a compact summary and `diff()` to diff the repo's uncommitted changes against the initial state.

<CodeGroup>
```tsx box.ts
const status = await box.git.status()
const diff = await box.git.diff()

console.log(status)
console.log(diff)
```

```python box.py
status = box.git.status()
diff = box.git.diff()

print(status)
print(diff)
```
</CodeGroup>

### Commit and push

After your code changes are ready, create a commit and push the branch.

<CodeGroup>
```tsx box.ts
const commit = await box.git.commit({
  message: "fix: handle empty state in dashboard",
})

await box.git.push({ branch: "fix/empty-state" })

console.log(commit.sha)
console.log(commit.message)
```

```python box.py
commit = box.git.commit(message="fix: handle empty state in dashboard")

box.git.push(branch="fix/empty-state")

print(commit.sha)
print(commit.message)
```
</CodeGroup>

### Open a pull request

Create a PR once your branch is pushed.

<CodeGroup>
```tsx box.ts
const pr = await box.git.createPR({
  title: "fix: handle empty state in dashboard",
  body: "Improves the dashboard empty state and avoids a broken loading flow.",
  base: "main",
})

console.log(pr.url)
```

```python box.py
pr = box.git.create_pr(
    title="fix: handle empty state in dashboard",
    body="Improves the dashboard empty state and avoids a broken loading flow.",
    base="main",
)

print(pr.url)
```
</CodeGroup>

***

## API

### Clone

Clones a repository into the current working directory in the box.

<CodeGroup>
```tsx box.ts
await box.git.clone({
  repo: "https://github.com/acme/api",
})

// 👇 you can also select a branch during clone:
await box.git.clone({
  repo: "https://github.com/acme/api",
  branch: "develop",
})
```

```python box.py
box.git.clone(repo="https://github.com/acme/api")

# 👇 you can also select a branch during clone:
box.git.clone(repo="https://github.com/acme/api", branch="develop")
```
</CodeGroup>

### Status

Returns the Git status output for the repository in the current working directory.

* See what files changed
* See if there are there untracked files

<CodeGroup>
```tsx box.ts
const status = await box.git.status()
```

```python box.py
status = box.git.status()
```
</CodeGroup>

### Diff

Returns the current Git diff as a string.

* Useful to display a patch in your UI
* Review what an agent changed

<CodeGroup>
```tsx box.ts
const diff = await box.git.diff()
```

```python box.py
diff = box.git.diff()
```
</CodeGroup>

### Commit

Creates a commit and returns commit information including the SHA and message.

<CodeGroup>
```tsx box.ts
const commit = await box.git.commit({
  message: "feat: add onboarding checklist",
})
```

```python box.py
commit = box.git.commit(message="feat: add onboarding checklist")
```
</CodeGroup>

You can optionally override the commit author for a single commit using `authorName` and `authorEmail`. When omitted, the box's configured git identity is used (either the values set at creation via `config.git` or the latest `updateConfig` values).

<CodeGroup>
```tsx box.ts
const commit = await box.git.commit({
  message: "feat: add onboarding checklist",
  authorName: "Alice",
  authorEmail: "alice@acme.com",
})
```

```python box.py
commit = box.git.commit(
    message="feat: add onboarding checklist",
    author_name="Alice",
    author_email="alice@acme.com",
)
```
</CodeGroup>

| Option        | Type     | Required | Description                                       |
| ------------- | -------- | -------- | ------------------------------------------------- |
| `message`     | `string` | Yes      | Commit message                                    |
| `authorName`  | `string` | No       | Override `--author` name for this commit only     |
| `authorEmail` | `string` | No       | Override `--author` email for this commit only    |

### Push

Pushes the current branch. You can also provide a branch name explicitly.

<CodeGroup>
```tsx box.ts
// 👇 push to default branch
await box.git.push()

// 👇 or to a specific branch
await box.git.push({
  branch: "feature/onboarding-checklist",
})
```

```python box.py
# 👇 push to default branch
box.git.push()

# 👇 or to a specific branch
box.git.push(branch="feature/onboarding-checklist")
```
</CodeGroup>

### Create a PR

Creates a pull request and returns the PR URL and metadata.

<CodeGroup>
```tsx box.ts
const pr = await box.git.createPR({
  title: "feat: add onboarding checklist",
  body: "Adds a simple onboarding checklist to improve first-run guidance.",
  base: "main",
})
```

```python box.py
pr = box.git.create_pr(
    title="feat: add onboarding checklist",
    body="Adds a simple onboarding checklist to improve first-run guidance.",
    base="main",
)
```
</CodeGroup>

### Checkout

Switches to another branch in the current repository.

<CodeGroup>
```tsx box.ts
await box.git.checkout({
  branch: "release/v1.2.0",
})
```

```python box.py
box.git.checkout(branch="release/v1.2.0")
```
</CodeGroup>

### Run a custom Git command

Runs a raw Git command and returns the command output. Useful escape hatch if the built-in helpers don't cover a use case.

<CodeGroup>
```tsx box.ts
const result = await box.git.exec({
  args: ["branch", "--show-current"],
})
```

```python box.py
result = box.git.exec(args=["branch", "--show-current"])
```
</CodeGroup>

### Update Git Config

Updates the git author identity in the running container. At least one field must be provided; the other field retains its current value.

Returns the effective identity values after the update.

<CodeGroup>
```tsx box.ts
const identity = await box.git.updateConfig({
  userName: "ci-bot",
  userEmail: "ci-bot@acme.com",
})

console.log(identity.git_user_name)  // "ci-bot"
console.log(identity.git_user_email) // "ci-bot@acme.com"
```

```python box.py
identity = box.git.update_config(user_name="ci-bot", user_email="ci-bot@acme.com")

print(identity.git_user_name)   # "ci-bot"
print(identity.git_user_email)  # "ci-bot@acme.com"
```
</CodeGroup>

You can update a single field — the other will not change:

<CodeGroup>
```tsx box.ts
// Only update the email, keep the existing user name
const identity = await box.git.updateConfig({
  userEmail: "ci-bot@acme.com",
})
```

```python box.py
# Only update the email, keep the existing user name
identity = box.git.update_config(user_email="ci-bot@acme.com")
```
</CodeGroup>

If the box is in Running or Idle state, the new config is applied immediately inside the container (equivalent to running `git config --global` for the updated fields). Changes take effect for all subsequent commits.

| Option       | Type     | Required | Description                  |
| ------------ | -------- | -------- | ---------------------------- |
| `userName`   | `string` | No       | New value for `user.name`    |
| `userEmail`  | `string` | No       | New value for `user.email`   |

**Return value:** `{ git_user_name: string; git_user_email: string }`

***

## Examples

### Use Git with an agent

If you configured a box agent, it also has full git access.

This is especially useful when the exact git steps are not known ahead of time. For example, if the user sends an open-ended request, you may not know in advance what branch name, commit message, or final push flow makes sense.

In that case, you can let the agent inspect the repository, decide what changes are needed, and handle the git workflow itself.

<CodeGroup>
```tsx box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  agent: {
    harness: Agent.ClaudeCode,
    model: "anthropic/claude-opus-4-6",
    apiKey: process.env.ANTHROPIC_API_KEY,
  },
  git: {
    token: process.env.GITHUB_TOKEN,
  },
})

await box.git.clone({
  repo: "https://github.com/acme/web-app",
  branch: "main",
})

await box.cd("web-app")

const run = await box.agent.run({
  prompt: `
Inspect this repository and fix the broken mobile navigation.

Create a branch with a sensible name, make the necessary code and test changes, commit the changes, then push the branch and open a pull request against main.`,
})
```

```python box.py
import os
from upstash_box import Box, Agent

box = Box.create(
    runtime="node",
    agent={
        "harness": Agent.CLAUDE_CODE,
        "model": "anthropic/claude-opus-4-6",
        "api_key": os.environ["ANTHROPIC_API_KEY"],
    },
    git={"token": os.environ["GITHUB_TOKEN"]},
)

box.git.clone(repo="https://github.com/acme/web-app", branch="main")

box.cd("web-app")

run = box.agent.run(
    prompt="""
Inspect this repository and fix the broken mobile navigation.

Create a branch with a sensible name, make the necessary code and test changes, commit the changes, then push the branch and open a pull request against main.""",
)
```
</CodeGroup>

### End-to-end PR automation

<CodeGroup>
```tsx box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  agent: {
    harness: Agent.ClaudeCode,
    model: "anthropic/claude-opus-4-6",
    apiKey: process.env.ANTHROPIC_API_KEY,
  },
  git: {
    token: process.env.GITHUB_TOKEN,
  },
})

await box.git.clone({
  repo: "https://github.com/acme/docs-site",
  branch: "main",
})

await box.cd("docs-site")

await box.git.exec({
  args: ["checkout", "-b", "docs/deployment-troubleshooting"],
})

await box.agent.run({
  prompt: "Add a troubleshooting section to the deployment guide.",
})

await box.git.commit({
  message: "docs: add deployment troubleshooting section",
})

await box.git.push({ branch: "docs/deployment-troubleshooting" })

const pr = await box.git.createPR({
  title: "docs: add deployment troubleshooting section",
  base: "main",
})
```

```python box.py
import os
from upstash_box import Box, Agent

box = Box.create(
    runtime="node",
    agent={
        "harness": Agent.CLAUDE_CODE,
        "model": "anthropic/claude-opus-4-6",
        "api_key": os.environ["ANTHROPIC_API_KEY"],
    },
    git={"token": os.environ["GITHUB_TOKEN"]},
)

box.git.clone(repo="https://github.com/acme/docs-site", branch="main")

box.cd("docs-site")

box.git.exec(args=["checkout", "-b", "docs/deployment-troubleshooting"])

box.agent.run(prompt="Add a troubleshooting section to the deployment guide.")

box.git.commit(message="docs: add deployment troubleshooting section")

box.git.push(branch="docs/deployment-troubleshooting")

pr = box.git.create_pr(
    title="docs: add deployment troubleshooting section",
    base="main",
)
```
</CodeGroup>

# Box Basics
Source: https://upstash.com/docs/box/overall/how-it-works

Every Upstash Box is a **durable execution environment** for AI workloads. Each box is an isolated container with its own filesystem, shell, network stack, and optional coding agent. You send prompts or commands, the box executes them, and you get back structured results without managing infrastructure.

Boxes are billed per active CPU time (not idle time), state persists across runs, and you can choose from Node, Python, Go or other runtimes. By default, a box auto-pauses when idle and can be resumed days or even weeks later. If you enable `keepAlive`, the box stays on between sessions for always-available workloads.

Looking for inspiration? Check out the [Use Cases](/docs/box/overall/use-cases) page.

***

## Architecture

Every box is a self-contained environment with five capabilities:

| Module         | Description                                                  |
| -------------- | ------------------------------------------------------------ |
| **Agent**      | Run a coding agent (Claude Code or Codex)                    |
| **Git**        | Clone repos, inspect diffs, and open pull requests           |
| **Shell**      | Execute OS-level commands directly                           |
| **Filesystem** | Upload, write, read, list, and download files inside the box |
| **Snapshots**  | Capture box state and restore new boxes from it              |

The agent has full access to the shell, filesystem, and git inside its box. It can install packages, write files, run tests, and interact with the network.

***

## Runtimes

Each box runs in an isolated container with a pre-installed language runtime. By default, all runtimes use **Debian** (glibc), which offers the widest binary compatibility. Append `-alpine` for smaller images based on musl.

| Runtime | Default (Debian) | Alpine variant    |
|---------|------------------|-------------------|
| Node.js | `node`           | `node-alpine`     |
| Python  | `python`         | `python-alpine`   |
| Go      | `golang`         | `golang-alpine`   |
| Ruby    | `ruby`           | `ruby-alpine`     |
| Rust    | `rust`           | `rust-alpine`     |

<CodeGroup>
```typescript box.ts
// Debian (default) — best for native modules and prebuilt binaries
const box = await Box.create({ runtime: "node" })

// Alpine — smaller image, musl-based
const box = await Box.create({ runtime: "node-alpine" })
```

```python box.py
# Debian (default) — best for native modules and prebuilt binaries
box = Box.create(runtime="node")

# Alpine — smaller image, musl-based
box = Box.create(runtime="node-alpine")
```
</CodeGroup>

***

## Agent

Every Upstash Box comes with built-in coding agent harnesses. You don't need to bring your own agent framework or wire up tool calls. The box already knows how to give an agent access to its shell, filesystem, and git, and how to stream output back to you.

We currently support Claude Code and Codex as native agents inside of a box. You choose a model when creating a box.

For more details, see the [Agent](/docs/box/overall/agent) page.

  <img />

Each iteration builds on the last. If a test fails, the agent sees the error output and corrects. If a file is missing, it discovers that during the read phase and adapts. The loop continues until the task is complete or the agent determines it cannot make further progress.

You control what goes in (the prompt) and what comes out (raw text or a structured response). The agent handles reasoning and tool selection within its box, using the same [shell](/docs/box/overall/shell), [filesystem](/docs/box/overall/files), and [git](/docs/box/overall/git) available to you through the SDK.

A box retains its full state between runs (files, installed packages, git history, etc.). You can send multiple prompts to the same box and the agent picks up exactly where it left off.

***

## Lifecycle

  <img />

### 1. Created

When you create a box, Upstash provisions a new isolated container with its own filesystem, shell, and network stack. You can start from a fresh box or restore from a snapshot. Once provisioning finishes, the box is ready to receive commands.

### 2. Running

The box automatically enters Running state after creation. Your agent can run bash commands, read and write files, interact with git, and make outbound network requests. `stdout` and `stderr` stream back in real-time.

If a box sits idle with no active commands, it automatically transitions to Paused after an idle timeout. Free plans auto-pause sooner than paid plans. If `keepAlive` is enabled, it stays on instead.

### 3. Paused

While a box is paused, it releases its compute resources but preserves the filesystem and environment. You can resume it explicitly or simply send new work to wake it back up.

By default, boxes are billed only on active usage, so a paused box does not incur active compute costs. Boxes with `keepAlive` enabled do not use pause/resume.

### 4. Snapshot

Snapshots capture the full workspace state of a box at a point in time. They let you checkpoint a working environment and later create a new box from that exact state.

Learn more in [Snapshots](/docs/box/overall/snapshots).

### 5. Deleted

Deleting a box permanently destroys the live box and its current state. This is irreversible, so take a snapshot first if you may need to restore the environment later.

Any existing snapshots taken from the box are not affected by deletion.

***

## API

Use the main Box APIs to create, reconnect, inspect, pause, snapshot, and delete boxes.

For work inside a box, use the dedicated [Shell](/docs/box/overall/shell), [Filesystem](/docs/box/overall/files), [Git](/docs/box/overall/git), and [Agent](/docs/box/overall/agent) APIs.

### Create or reconnect a box

Most apps need three entry points: start a new box, show the boxes that already exist, or reopen one from a saved ID.

<CodeGroup>
```typescript box.ts
const box = await Box.create({ runtime: "node" })
const boxes = await Box.list()

// 👇 later, or in another serverless function
const sameBox = await Box.get(box.id)
```

```python box.py
box = Box.create(runtime="node")
boxes = Box.list()

# 👇 later, or in another process
same_box = Box.get(box.id)
```
</CodeGroup>

You can also give a box a name and fetch it by name:

<CodeGroup>
```typescript box.ts
const box = await Box.create({ runtime: "node", name: "my-worker" })

// 👇 fetch by name
const sameBox = await Box.getByName("my-worker")
```

```python box.py
box = Box.create(runtime="node", name="my-worker")

# 👇 fetch by name
same_box = Box.get_by_name("my-worker")
```
</CodeGroup>

This is the core lifecycle entrypoint for dashboards, background workers, and apps that persist a box ID or name between requests.

If you want to open an interactive shell directly, you can also connect over SSH:

```bash
ssh <box-id>@us-east-1.box.upstash.com
```

Use your **Box API key** as the SSH password.

### Box size

Boxes have configurable resource sizes, set at creation time via the `size` option. Defaults to `"small"`.

| Size     | CPU     | Memory |
| -------- | ------- | ------ |
| `small`  | 2 cores | 4 GB   |
| `medium` | 4 cores | 8 GB   |
| `large`  | 8 cores | 16 GB  |

<CodeGroup>
```typescript box.ts
const box = await Box.create({
  size: "large",
  agent: { harness: Agent.ClaudeCode, model: "anthropic/claude-sonnet-4-5" },
})

console.log(box.size) // "large"
```

```python box.py
box = Box.create(
    size="large",
    agent={"harness": Agent.CLAUDE_CODE, "model": "anthropic/claude-sonnet-4-5"},
)

print(box.size)  # "large"
```
</CodeGroup>

Also supported in `Box.fromSnapshot()`:

<CodeGroup>
```typescript box.ts
const box = await Box.fromSnapshot("snap_abc123", { size: "medium" })
```

```python box.py
box = Box.from_snapshot("snap_abc123", size="medium")
```
</CodeGroup>

### Check status and inspect activity

<CodeGroup>
```typescript box.ts
const { status } = await box.getStatus()
const logs = await box.logs()
const runs = await box.listRuns()
```

```python box.py
status = box.get_status()["status"]
logs = box.logs()
runs = box.list_runs()
```
</CodeGroup>

These APIs are useful when you are building monitoring, history, or debugging views.

### Pause or resume

Pausing is useful when work arrives in bursts and you want to keep the environment intact without paying for active compute the whole time.

<CodeGroup>
```typescript box.ts
await box.pause()
await box.resume()
```

```python box.py
box.pause()
box.resume()
```
</CodeGroup>

Paused boxes do not accrue active CPU charges. Pause/resume is not available when `keepAlive` is enabled.

### Snapshot and restore

Snapshots are the best way to turn a prepared environment into a reusable starting point, especially installing dependencies.

<CodeGroup>
```typescript box.ts
const snapshot = await box.snapshot({ name: "after-setup" })
const restored = await Box.fromSnapshot(snapshot.id)
```

```python box.py
snapshot = box.snapshot(name="after-setup")
restored = Box.from_snapshot(snapshot.id)
```
</CodeGroup>

This is useful for reusable base environments, checkpoints before risky work, and branching multiple boxes from the same setup state.

For detailed snapshot workflows, see [Snapshots](/docs/box/overall/snapshots).

### Delete

Once a run is finished, delete the live box if you don't need it anymore:

<CodeGroup>
```typescript box.ts
await box.delete()
```

```python box.py
box.delete()
```
</CodeGroup>

Deleting a box is irreversible, so snapshot first if you may want to recreate the same environment later.

***

## Networking

Every box has full outbound network access by default. HTTP, HTTPS, DNS, WebSockets, and raw TCP are all available. Agents can call external APIs, download packages, pull container images, and interact with any public endpoint.

Boxes run on AWS infrastructure with **22.5 Gbps** network bandwidth per host. This means large file transfers, dataset downloads, and parallel API calls are fast by default.

Because boxes run on fast AWS infrastructure, they have single-digit ms to major cloud services (S3, GitHub, etc.).

### Network Policy

You can control outbound network access for a box with a network policy. For modes, examples, and SDK usage, see [Network Policy](/docs/box/overall/network-policy).

***

## Security & Isolation

Every box runs as its own Docker container with an independent filesystem, process tree, and network stack. Boxes cannot communicate with or observe each other. There is no shared state between them.

  <img />

Your app makes SDK calls to the Upstash API gateway, which authenticates the request and routes it to the correct box. Each box has a unique ID, and all communication between your app and the box is encrypted in transit.

Inside a box, the agent, shell, filesystem, and git all share the same isolated environment. The agent can install packages, write files, spawn processes, and make outbound HTTP requests, but only within its own container boundary. It cannot access the host, other boxes, or any Upstash-internal infrastructure.

| Boundary       | Guarantee                                                                               |
| -------------- | --------------------------------------------------------------------------------------- |
| **Filesystem** | Each box has its own filesystem. No shared volumes between boxes.                       |
| **Processes**  | Process trees are fully isolated. One box cannot signal or inspect another's processes. |
| **Network**    | Boxes can make outbound requests (HTTP, DNS) but cannot reach other boxes.              |

***

## Compute & Billing

Boxes are billed on active usage by default. If `keepAlive` is enabled, billing switches to an always-on model because the box stays running between sessions.

Boxes are available in three sizes:

| Size | CPU | Memory | Storage |
| --- | --- | --- | --- |
| `small` | 2 vCPU | 4 GB | 5 GB |
| `medium` | 4 vCPU | 8 GB | 10 GB |
| `large` | 8 vCPU | 16 GB | 20 GB |

For exact pricing details, see the [pricing page](https://upstash.com/pricing/box). For keep-alive behavior, see [Keep Alive](/docs/box/overall/keep-alive).

# Keep Alive
Source: https://upstash.com/docs/box/overall/keep-alive

When `keepAlive` is enabled, the box stays on between sessions instead of auto-pausing when idle. Use it when you need a box to remain continuously available, for example to host a server, keep a long-running agent ready, or preserve an always-on development environment.

For most workloads, the default Box lifecycle is still the better choice. Boxes auto-pause when idle and are more cost-efficient for bursty workloads unless `keepAlive` is enabled.

***

## Create a keep-alive box

Use `keepAlive: true` when creating the box:

<CodeGroup>
```typescript box.ts
import { Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  size: "medium",
  keepAlive: true,
})
```

```python box.py
from upstash_box import Box

box = Box.create(
    runtime="node",
    size="medium",
    keep_alive=True,
)
```
</CodeGroup>

You can combine keep-alive with all normal Box features, including agent, git, shell, filesystem, public URLs, and snapshots.

***

## Sizes and specs

Boxes with `keepAlive` enabled use the same Box sizes as any other box. Pick the size with the `size` option when creating the box or in the Console during box creation.

| Size | CPU | Memory | Storage |
| --- | --- | --- | --- |
| `small` | 2 vCPU | 4 GB | 5 GB |
| `medium` | 4 vCPU | 8 GB | 10 GB |
| `large` | 8 vCPU | 16 GB | 20 GB |

The selected size determines the box's available CPU, RAM, and workspace storage whether `keepAlive` is enabled or not.

***

## Lifecycle differences

| | Default behavior | With `keepAlive: true` |
| --- | --- | --- |
| **Idle behavior** | Auto-pauses when idle | Stays on |
| **Pause / resume** | Supported | Not supported |
| **Best for** | Bursty workloads, task runners, on-demand agents | Always-on servers, long-running agents, warm environments |
| **Billing model** | Active usage based | Open-time / always-on based |

If you do not need the box to remain continuously available, keep `keepAlive` disabled.

***

## Init command

Boxes with `keepAlive` enabled can run a startup command whenever the box starts.

<CodeGroup>
```typescript box.ts
const box = await Box.create({
  runtime: "node",
  keepAlive: true,
  initCommand: "npm install && npm run dev",
})
```

```python box.py
box = Box.create(
    runtime="node",
    keep_alive=True,
    init_command="npm install && npm run dev",
)
```
</CodeGroup>

This is useful for:

* starting a web server
* launching a background process
* preparing a long-running agent environment
* restoring a development workflow automatically after the box starts

You can also manage the init command after creation:

<CodeGroup>
```typescript box.ts
await box.setInitCommand("npm run dev")

const command = await box.getInitCommand()
await box.deleteInitCommand()

console.log(box.keepAlive) // true
```

```python box.py
box.set_init_command("npm run dev")

command = box.get_init_command()
box.delete_init_command()

print(box.keep_alive)  # True
```
</CodeGroup>

Init command management is only available when `keepAlive` is enabled.

***

## Console

In the Upstash Console you can:

* enable **Keep alive** while creating a box
* choose the box **Size**
* manage the **Init Command** later from the box settings page

***

## When to use keep alive

Use keep-alive when the box itself needs to stay available between requests:

* hosting a dev server or app with a public URL
* keeping an agent warm for low-latency use
* preserving a long-running environment with startup automation

Avoid keep-alive when you only need a reusable workspace. In those cases, the default box lifecycle plus [Snapshots](/docs/box/overall/snapshots) is usually enough.

# Network Policy
Source: https://upstash.com/docs/box/overall/network-policy

Network policies control outbound network access from a box.

Use them when you want to:

* block all outbound traffic
* allow only specific public domains
* restrict egress to specific CIDR ranges

By default, boxes use:

<CodeGroup>
```typescript box.ts
{ mode: "allow-all" }
```

```python box.py
{"mode": "allow-all"}
```
</CodeGroup>

***

## Modes

| Mode | Description |
| --- | --- |
| `allow-all` | Default. No outbound restrictions. |
| `deny-all` | Block all outbound network access. |
| `custom` | Allow or deny specific domains and CIDR ranges. |

The SDK type is:

<CodeGroup>
```typescript box.ts
type NetworkPolicy =
  | { mode: "allow-all" | "deny-all" }
  | {
      mode: "custom"
      allowedDomains?: string[]
      allowedCidrs?: string[]
      deniedCidrs?: string[]
    }
```

```python box.py
from typing import Literal, TypedDict, Union
from typing_extensions import NotRequired

class AllowDenyNetworkPolicy(TypedDict):
    mode: Literal["allow-all", "deny-all"]

class CustomNetworkPolicy(TypedDict):
    mode: Literal["custom"]
    allowed_domains: NotRequired[list[str]]
    allowed_cidrs: NotRequired[list[str]]
    denied_cidrs: NotRequired[list[str]]

NetworkPolicy = Union[AllowDenyNetworkPolicy, CustomNetworkPolicy]
```
</CodeGroup>

***

## Create a box with a policy

Pass `networkPolicy` when creating a box:

<CodeGroup>
```typescript box.ts
import { Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  networkPolicy: {
    mode: "custom",
    allowedDomains: ["api.github.com", "registry.npmjs.org"],
  },
})
```

```python box.py
from upstash_box import Box

box = Box.create(
    runtime="node",
    network_policy={
        "mode": "custom",
        "allowed_domains": ["api.github.com", "registry.npmjs.org"],
    },
)
```
</CodeGroup>

You can also combine domain and CIDR rules:

<CodeGroup>
```typescript box.ts
const box = await Box.create({
  runtime: "node",
  networkPolicy: {
    mode: "custom",
    allowedDomains: ["api.github.com", "*.githubusercontent.com"],
    allowedCidrs: ["104.16.0.0/12"],
  },
})
```

```python box.py
box = Box.create(
    runtime="node",
    network_policy={
        "mode": "custom",
        "allowed_domains": ["api.github.com", "*.githubusercontent.com"],
        "allowed_cidrs": ["104.16.0.0/12"],
    },
)
```
</CodeGroup>

`networkPolicy` is also supported in `Box.fromSnapshot()` and `EphemeralBox`.

***

## Read the current policy

Use the `networkPolicy` getter:

<CodeGroup>
```typescript box.ts
console.log(box.networkPolicy) // { mode: "allow-all" }
```

```python box.py
print(box.network_policy)  # {"mode": "allow-all"}
```
</CodeGroup>

***

## Update a running box

Update the policy after creation:

<CodeGroup>
```typescript box.ts
await box.updateNetworkPolicy({ mode: "deny-all" })
```

```python box.py
box.update_network_policy({"mode": "deny-all"})
```
</CodeGroup>

Switch back to unrestricted outbound access:

<CodeGroup>
```typescript box.ts
await box.updateNetworkPolicy({ mode: "allow-all" })
```

```python box.py
box.update_network_policy({"mode": "allow-all"})
```
</CodeGroup>

Changes take effect immediately. You do not need to recreate the box.

***

## Matching rules

* `allowedDomains` supports exact matches such as `api.github.com`
* wildcard domains must use `*.suffix` form, for example `*.githubusercontent.com`
* `allowedCidrs` and `deniedCidrs` use standard CIDR notation
* in `custom` mode, `deniedCidrs` takes precedence over allowed CIDRs
* private IP ranges are always blocked even if you try to allow them explicitly

***

## Example patterns

Allow only GitHub and npm:

<CodeGroup>
```typescript box.ts
await box.updateNetworkPolicy({
  mode: "custom",
  allowedDomains: ["github.com", "*.github.com", "registry.npmjs.org"],
})
```

```python box.py
box.update_network_policy({
    "mode": "custom",
    "allowed_domains": ["github.com", "*.github.com", "registry.npmjs.org"],
})
```
</CodeGroup>

Block all outbound traffic:

<CodeGroup>
```typescript box.ts
await box.updateNetworkPolicy({ mode: "deny-all" })
```

```python box.py
box.update_network_policy({"mode": "deny-all"})
```
</CodeGroup>

Allow a specific public CIDR:

<CodeGroup>
```typescript box.ts
await box.updateNetworkPolicy({
  mode: "custom",
  allowedCidrs: ["104.16.0.0/12"],
})
```

```python box.py
box.update_network_policy({
    "mode": "custom",
    "allowed_cidrs": ["104.16.0.0/12"],
})
```
</CodeGroup>

Block a specific CIDR range:

<CodeGroup>
```typescript box.ts
await box.updateNetworkPolicy({
  mode: "custom",
  deniedCidrs: ["104.16.120.0/24"],
})
```

```python box.py
box.update_network_policy({
    "mode": "custom",
    "denied_cidrs": ["104.16.120.0/24"],
})
```
</CodeGroup>

***

## Related

* [Security](/docs/box/overall/security)
* [Shell](/docs/box/overall/shell)
* [How It Works](/docs/box/overall/how-it-works)

# Public URLs
Source: https://upstash.com/docs/box/overall/preview

Expose ports from a box with public URLs. Each public URL maps to a specific port and can optionally require authentication.

***

## Quickstart

### Create a public URL

Start a web server inside your box and create a public URL to access it.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({ runtime: "node" })

// Start a web server on port 3000
await box.exec.command("cd /work && npm install express")
await box.files.write({
  path: "/work/server.js",
  content: `
    const express = require('express')
    const app = express()
    app.get('/', (req, res) => res.send('Hello from Box!'))
    app.listen(3000)
  `,
})
await box.exec.command("node /work/server.js &")

// Create a public URL
const publicUrl = await box.getPublicURL(3000)

console.log(publicUrl.url)
// → https://{BOX_ID}-3000.preview.box.upstash.com
```

```python box.py
from upstash_box import Box

box = Box.create(runtime="node")

# Start a web server on port 3000
box.exec.command("cd /work && npm install express")
box.files.write(
    path="/work/server.js",
    content="""
    const express = require('express')
    const app = express()
    app.get('/', (req, res) => res.send('Hello from Box!'))
    app.listen(3000)
  """,
)
box.exec.command("node /work/server.js &")

# Create a public URL
public_url = box.get_public_url(3000)

print(public_url.url)
# -> https://{BOX_ID}-3000.preview.box.upstash.com
```
</CodeGroup>

### Add authentication

Protect your public URL with bearer token or basic authentication.

<CodeGroup>
```typescript box.ts
// With bearer token
const publicUrl = await box.getPublicURL(3000, { bearerToken: true })

console.log(publicUrl.token) // Use this in Authorization header
// → "63d8b153..."

// With basic auth
const publicUrl = await box.getPublicURL(8080, { basicAuth: true })

console.log(publicUrl.username) // → "user"
console.log(publicUrl.password) // → "f0f145f0..."
```

```python box.py
# With bearer token
public_url = box.get_public_url(3000, bearer_token=True)

print(public_url.token)  # Use this in Authorization header
# -> "63d8b153..."

# With basic auth
public_url = box.get_public_url(8080, basic_auth=True)

print(public_url.username)  # -> "user"
print(public_url.password)  # -> "f0f145f0..."
```
</CodeGroup>

***

## API

### Create Public URL

Creates a public URL that exposes a port on your box. Returns the URL and authentication credentials if requested.

<CodeGroup>
```typescript box.ts
const publicUrl = await box.getPublicURL(3000)

console.log(publicUrl.url)
// → https://{BOX_ID}-3000.preview.box.upstash.com
```

```python box.py
public_url = box.get_public_url(3000)

print(public_url.url)
# -> https://{BOX_ID}-3000.preview.box.upstash.com
```
</CodeGroup>

#### With Bearer Token

Add `bearerToken: true` to require an authorization header when accessing the public URL.

<CodeGroup>
```typescript box.ts
const publicUrl = await box.getPublicURL(3000, { bearerToken: true })

console.log(publicUrl.token)
// → "63d8b153..."

// Access the public URL:
// curl -H "Authorization: Bearer 63d8b153..." https://{BOX_ID}-3000.preview.box.upstash.com
```

```python box.py
public_url = box.get_public_url(3000, bearer_token=True)

print(public_url.token)
# -> "63d8b153..."

# Access the public URL:
# curl -H "Authorization: Bearer 63d8b153..." https://{BOX_ID}-3000.preview.box.upstash.com
```
</CodeGroup>

#### With Basic Authentication

Add `basicAuth: true` to require username and password when accessing the public URL.

<CodeGroup>
```typescript box.ts
const publicUrl = await box.getPublicURL(8080, { basicAuth: true })

console.log(publicUrl.username) // → "user"
console.log(publicUrl.password) // → "f0f145f0..."

// Access the public URL:
// curl -u user:f0f145f0... https://{BOX_ID}-8080.preview.box.upstash.com
```

```python box.py
public_url = box.get_public_url(8080, basic_auth=True)

print(public_url.username)  # -> "user"
print(public_url.password)  # -> "f0f145f0..."

# Access the public URL:
# curl -u user:f0f145f0... https://{BOX_ID}-8080.preview.box.upstash.com
```
</CodeGroup>

#### With Both Auth Methods

Enable both authentication methods. Either one will work when accessing the public URL.

<CodeGroup>
```typescript box.ts
const publicUrl = await box.getPublicURL(8080, { bearerToken: true, basicAuth: true })

console.log(publicUrl.token) // → "63d8b153..."
console.log(publicUrl.username) // → "user"
console.log(publicUrl.password) // → "f0f145f0..."
```

```python box.py
public_url = box.get_public_url(8080, bearer_token=True, basic_auth=True)

print(public_url.token)     # -> "63d8b153..."
print(public_url.username)  # -> "user"
print(public_url.password)  # -> "f0f145f0..."
```
</CodeGroup>

***

### List Public URLs

Get all active public URLs for this box.

<CodeGroup>
```typescript box.ts
const { publicURLs } = await box.listPublicURLs()

console.log(publicURLs)
// [
//   { url: "https://{BOX_ID}-3000.preview.box.upstash.com", port: 3000 },
//   { url: "https://{BOX_ID}-8080.preview.box.upstash.com", port: 8080 },
// ]
```

```python box.py
result = box.list_public_urls()

print(result["public_urls"])
# [
#   PublicURL(url="https://{BOX_ID}-3000.preview.box.upstash.com", port=3000),
#   PublicURL(url="https://{BOX_ID}-8080.preview.box.upstash.com", port=8080),
# ]
```
</CodeGroup>

***

### Delete Public URL

Remove a public URL by port number.

<CodeGroup>
```typescript box.ts
await box.deletePublicURL(3000)
```

```python box.py
box.delete_public_url(3000)
```
</CodeGroup>

***

## Behavior

### One Public URL Per Port

Creating a public URL for a port that already has one will overwrite the previous one, including any auth credentials.

<CodeGroup>
```typescript box.ts
// First public URL
const publicUrl1 = await box.getPublicURL(3000)

// Second public URL overwrites the first one
const publicUrl2 = await box.getPublicURL(3000, { bearerToken: true })

// publicUrl1.url is no longer accessible
```

```python box.py
# First public URL
public_url1 = box.get_public_url(3000)

# Second public URL overwrites the first one
public_url2 = box.get_public_url(3000, bearer_token=True)

# public_url1.url is no longer accessible
```
</CodeGroup>

### Public URL Lifecycle

Public URLs expire automatically when:
* The box is paused
* The box is deleted

### Auto-Resume

Creating a public URL on a paused box automatically resumes it.

<CodeGroup>
```typescript box.ts
await box.pause()

// This will resume the box
const publicUrl = await box.getPublicURL(3000)
```

```python box.py
box.pause()

# This will resume the box
public_url = box.get_public_url(3000)
```
</CodeGroup>

***

## Examples

### Expose an agent-built app

Let an agent build a web app, then create a public URL to test it.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  agent: {
    harness: Agent.ClaudeCode,
    model: "anthropic/claude-opus-4-6",
    apiKey: process.env.ANTHROPIC_API_KEY,
  },
})

await box.agent.run({
  prompt: `
Create a simple Express web server that:
- Listens on port 3000
- Has a / route that returns "Hello World"
- Has a /health route that returns {"status": "ok"}
- Start the server in the background
  `,
})

const publicUrl = await box.getPublicURL(3000)
console.log(`Public URL available at: ${publicUrl.url}`)

// Test the endpoints
const response = await fetch(`${publicUrl.url}/health`)
console.log(await response.json()) // { status: "ok" }
```

```python box.py
import os
from upstash_box import Box, Agent

box = Box.create(
    runtime="node",
    agent={
        "harness": Agent.CLAUDE_CODE,
        "model": "anthropic/claude-opus-4-6",
        "api_key": os.environ["ANTHROPIC_API_KEY"],
    },
)

box.agent.run(
    prompt="""
Create a simple Express web server that:
- Listens on port 3000
- Has a / route that returns "Hello World"
- Has a /health route that returns {"status": "ok"}
- Start the server in the background
  """,
)

public_url = box.get_public_url(3000)
print(f"Public URL available at: {public_url.url}")

# Test the endpoints
import httpx
response = httpx.get(f"{public_url.url}/health")
print(response.json())  # {"status": "ok"}
```
</CodeGroup>

### Share a secure public URL

Create a public URL with authentication for sharing with team members.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({ runtime: "python" })

// Set up a Flask app
await box.files.write({
  path: "/work/app.py",
  content: `
from flask import Flask
app = Flask(__name__)

@app.route('/')
def home():
    return 'Internal Dashboard'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)
  `,
})

await box.exec.command("pip install flask && python /work/app.py &")

// Create authenticated public URL
const publicUrl = await box.getPublicURL(8080, { basicAuth: true })

console.log(`Public URL: ${publicUrl.url}`)
console.log(`Username: ${publicUrl.username}`)
console.log(`Password: ${publicUrl.password}`)

// Share these credentials with your team
```

```python box.py
import httpx
from upstash_box import Box

box = Box.create(runtime="python")

# Set up a Flask app
box.files.write(
    path="/work/app.py",
    content="""
from flask import Flask
app = Flask(__name__)

@app.route('/')
def home():
    return 'Internal Dashboard'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)
  """,
)

box.exec.command("pip install flask && python /work/app.py &")

# Create authenticated public URL
public_url = box.get_public_url(8080, basic_auth=True)

print(f"Public URL: {public_url.url}")
print(f"Username: {public_url.username}")
print(f"Password: {public_url.password}")

# Share these credentials with your team
```
</CodeGroup>

### Multi-port application

Create multiple public URLs for different services in the same box.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({ runtime: "node" })

// Start frontend on port 3000
await box.files.write({
  path: "/work/frontend.js",
  content: `
const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('Frontend'))
app.listen(3000)
  `,
})

// Start API on port 8080
await box.files.write({
  path: "/work/api.js",
  content: `
const express = require('express')
const app = express()
app.get('/api/status', (req, res) => res.json({ status: 'ok' }))
app.listen(8080)
  `,
})

await box.exec.command("npm install express")
await box.exec.command("node /work/frontend.js & node /work/api.js &")

// Create a public URL for each service
const frontendPublicUrl = await box.getPublicURL(3000)
const apiPublicUrl = await box.getPublicURL(8080)

console.log(`Frontend: ${frontendPublicUrl.url}`)
console.log(`API: ${apiPublicUrl.url}`)
```

```python box.py
from upstash_box import Box

box = Box.create(runtime="node")

# Start frontend on port 3000
box.files.write(
    path="/work/frontend.js",
    content="""
const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('Frontend'))
app.listen(3000)
  """,
)

# Start API on port 8080
box.files.write(
    path="/work/api.js",
    content="""
const express = require('express')
const app = express()
app.get('/api/status', (req, res) => res.json({ status: 'ok' }))
app.listen(8080)
  """,
)

box.exec.command("npm install express")
box.exec.command("node /work/frontend.js & node /work/api.js &")

# Create a public URL for each service
frontend_public_url = box.get_public_url(3000)
api_public_url = box.get_public_url(8080)

print(f"Frontend: {frontend_public_url.url}")
print(f"API: {api_public_url.url}")
```
</CodeGroup>

### Temporary testing public URL

Create a public URL, run tests against it, then clean up.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({ runtime: "node" })

// Start test server
await box.exec.command("npx http-server /work -p 3000 &")

// Create a public URL for testing
const publicUrl = await box.getPublicURL(3000)

// Run your tests against the public URL
const response = await fetch(publicUrl.url)
console.log(response.status) // 200

// Clean up
await box.deletePublicURL(3000)
await box.delete()
```

```python box.py
import httpx
from upstash_box import Box

box = Box.create(runtime="node")

# Start test server
box.exec.command("npx http-server /work -p 3000 &")

# Create a public URL for testing
public_url = box.get_public_url(3000)

# Run your tests against the public URL
response = httpx.get(public_url.url)
print(response.status_code)  # 200

# Clean up
box.delete_public_url(3000)
box.delete()
```
</CodeGroup>

<Note>The SDK still supports `getPreviewUrl`, `listPreviews`, and `deletePreview`, but they are deprecated aliases for `getPublicURL`, `listPublicURLs`, and `deletePublicURL`.</Note>

# Pricing & Limits
Source: https://upstash.com/docs/box/overall/pricing

Please check our [pricing page](https://upstash.com/pricing/box) for the most up-to-date information on pricing and limits.

# Quickstart
Source: https://upstash.com/docs/box/overall/quickstart

import { Catalog } from "/snippets/catalog.jsx";

**Upstash Box lets you give your AI agents a computer.**

Every Upstash Box is a **secure, isolated cloud container with an AI Agent built-in**. Spin up as many as you want in parallel. Each one includes a full environment with a filesystem, shell, git, and a runtime. Your agent can read files, write code, and execute tasks inside it.

### 1. Get your API key

Go to the [Upstash Console](https://console.upstash.com) and create an API key.

  <img />

### 2. Install the SDK

<CodeGroup>
```bash npm
npm install @upstash/box
```

```bash yarn
yarn add @upstash/box
```

```bash pnpm
pnpm add @upstash/box
```

```bash bun
bun install @upstash/box
```

```bash pip
pip install upstash-box
```

</CodeGroup>

### 3. Set API Key

```bash title=".env"
UPSTASH_BOX_API_KEY=box_xxxxxxxxxxxxxxxxxxxxxxxx
```

### 4. Create a Box

<CodeGroup>
```typescript title="lib/box.ts"
import { Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
})
```

```python box.py
from upstash_box import Box

box = Box.create(runtime="node")
```
</CodeGroup>

<Tip>
  The Python SDK ships both a synchronous `Box` (used here) and an asynchronous
  `AsyncBox` (`box = await AsyncBox.create(...)`).
</Tip>

<Tip>
  By default, runtimes use **Debian** (glibc). For smaller Alpine-based images, use `"node-alpine"`, `"python-alpine"`, etc.
</Tip>

Your box is ready to use! You can already use it as a standalone, secure, isolated sandbox with full shell access, git, and filesystem operations.

<Tip>
  You can also create a keep-alive box by setting `keepAlive: true`. Keep-alive boxes stay on between sessions and can run an `initCommand` at startup. See [Keep Alive](/docs/box/overall/keep-alive).
</Tip>

***

### 5. Configure an Agent (optional)

To configure an agent for your box, pass the model you want to use and the provider API key:

<Tabs>
<Tab title="Claude">
<CodeGroup>
```typescript box.ts {5-8}
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  agent: {
    harness: Agent.ClaudeCode,
    model: "anthropic/claude-opus-4-6",
    apiKey: process.env.ANTHROPIC_API_KEY,
  },
})
```

```python box.py {6-10}
import os
from upstash_box import Box, Agent

box = Box.create(
    runtime="node",
    agent={
        "harness": Agent.CLAUDE_CODE,
        "model": "anthropic/claude-opus-4-6",
        "api_key": os.environ["ANTHROPIC_API_KEY"],
    },
)
```
</CodeGroup>
</Tab>

<Tab title="Codex">
<CodeGroup>
```typescript box.ts {5-8}
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  agent: {
    harness: Agent.Codex,
    model: "openai/gpt-5.3-codex",
    apiKey: process.env.OPENAI_API_KEY,
  },
})
```

```python box.py {6-10}
import os
from upstash_box import Box, Agent

box = Box.create(
    runtime="node",
    agent={
        "harness": Agent.CODEX,
        "model": "openai/gpt-5.3-codex",
        "api_key": os.environ["OPENAI_API_KEY"],
    },
)
```
</CodeGroup>
</Tab>
</Tabs>

### 6. Run Your First Task

Your box is ready to use! It's a secure cloud environment to run agents, execute commands, or manage files. For example:

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  agent: {
    harness: Agent.ClaudeCode,
    model: "anthropic/claude-opus-4-6",
    apiKey: process.env.ANTHROPIC_API_KEY,
  },
})

// 👇 execute OS-level commands
await box.exec.command("node --version")

// 👇 run agent
await box.agent.run({
  prompt: "create an index.txt saying 'hello world'",
})
```

```python box.py
import os
from upstash_box import Box, Agent

box = Box.create(
    runtime="node",
    agent={
        "harness": Agent.CLAUDE_CODE,
        "model": "anthropic/claude-opus-4-6",
        "api_key": os.environ["ANTHROPIC_API_KEY"],
    },
)

# 👇 execute OS-level commands
box.exec.command("node --version")

# 👇 run agent
box.agent.run(prompt="create an index.txt saying 'hello world'")
```
</CodeGroup>

### 7. Connect over SSH

You can also connect directly to a box shell with SSH:

```bash
ssh <box-id>@us-east-1.box.upstash.com
```

When SSH asks for a password, enter your **Box API key**.

You can copy the exact SSH command for a box from the **SSH** button on its details page in the Upstash Console.

***

## Use Cases

The idea behind Upstash Box is simple: **give AI its own computer**. Your agent gets a full, isolated cloud environment it can control. Run commands, write files, or execute code independent of any user device. Freeze a box anytime, and continue days or even weeks later with perfect resumability.

Great example use cases:

<CardGroup cols={3}>
  <Card title="Agent Servers" icon="server" href="/box/overall/how-it-works#1-agent-server">
    One box per user with durable state. Personalized agents that remember context and improve over time.
  </Card>

  <Card title="Multi-Agent Orchestration" icon="diagram-project" href="/box/overall/how-it-works#2-multi-agent-orchestration">
    Fan out to multiple boxes running specialized agents in parallel, then combine their results.
  </Card>

  <Card title="Parallel Testing" icon="flask-vial" href="/box/overall/how-it-works#3-parallel-testing--model-comparison">
    Run the same inputs across isolated boxes and compare model output side by side.
  </Card>
</CardGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="How Boxes work" href="/box/overall/how-it-works">
    Learn the basics about using Upstash Box.
  </Card>

  <Card title="Agent" href="/box/overall/agent">
    Boxes have Claude Code, Codex or OpenCode built-in.
  </Card>
</CardGroup>

## SDKs

<Catalog product="box" type="sdk" cols={3} />

# Schedules
Source: https://upstash.com/docs/box/overall/schedules

Create recurring tasks that run on a cron schedule inside a box. You can schedule shell commands or agent prompts. Available on both `box.schedule` and `ephemeralBox.schedule`.

***

## API

### Schedule a shell command

Run a shell command on a cron schedule.

<CodeGroup>
```typescript box.ts
const schedule = await box.schedule.exec({
  cron: "* * * * *",
  command: ["bash", "-c", "date >> /workspace/home/cron.log && echo scheduled-ok"],
  webhookUrl: "https://example.com/hook",          // optional
  webhookHeaders: { Authorization: "Bearer ..." }, // optional
  folder: "/workspace/home",                       // optional, defaults to box.cwd
})

console.log(schedule.id)
```

```python box.py
schedule = box.schedule.exec(
    cron="* * * * *",
    command=["bash", "-c", "date >> /workspace/home/cron.log && echo scheduled-ok"],
    webhook_url="https://example.com/hook",            # optional
    webhook_headers={"Authorization": "Bearer ..."},   # optional
    folder="/workspace/home",                          # optional, defaults to box.cwd
)

print(schedule.id)
```
</CodeGroup>

| Parameter | Required | Description |
| --- | --- | --- |
| `cron` | Yes | 5-field cron expression (UTC) |
| `command` | Yes | `string[]` — the command to run |
| `webhookUrl` | No | URL to notify when the scheduled run completes |
| `webhookHeaders` | No | Headers to include in the webhook request |
| `folder` | No | Working directory. Defaults to `box.cwd`; relative paths resolved against it |

***

### Schedule an agent prompt

Run an agent prompt on a cron schedule. Requires an agent to be configured on the box.

<CodeGroup>
```typescript box.ts
const schedule = await box.schedule.agent({
  cron: "0 9 * * *",
  prompt: "Run the test suite and fix any failures",
  model: "anthropic/claude-sonnet-4-6",          // optional, overrides box's model
  timeout: 300_000,                     // optional, kill the run after 5 minutes
  options: { maxBudgetUsd: 1.0 },      // optional, provider-specific agent options
})

console.log(schedule.id)
```

```python box.py
schedule = box.schedule.agent(
    cron="0 9 * * *",
    prompt="Run the test suite and fix any failures",
    model="anthropic/claude-sonnet-4-6",   # optional, overrides box's model
    timeout=300_000,                        # optional, kill the run after 5 minutes
    options={"max_budget_usd": 1.0},        # optional, provider-specific agent options
)

print(schedule.id)
```
</CodeGroup>

| Parameter | Required | Description |
| --- | --- | --- |
| `cron` | Yes | 5-field cron expression (UTC) |
| `prompt` | Yes | The prompt to send to the agent |
| `folder` | No | Working directory. Defaults to `box.cwd`; relative paths resolved against it |
| `model` | No | Override the box's default model for this schedule |
| `options` | No | Provider-specific agent options (e.g. `maxBudgetUsd`, `effort`, `systemPrompt`, `maxTurns`) |
| `timeout` | No | Timeout in milliseconds — kills the run if exceeded |
| `webhookUrl` | No | URL to notify when the scheduled run completes |
| `webhookHeaders` | No | Headers to include in the webhook request |

***

### List schedules

Retrieve all non-deleted schedules for the current box.

<CodeGroup>
```typescript box.ts
const schedules = await box.schedule.list()

for (const s of schedules) {
  console.log(s.id, s.type, s.cron, s.status)
}
```

```python box.py
schedules = box.schedule.list()

for s in schedules:
    print(s.id, s.type, s.cron, s.status)
```
</CodeGroup>

***

### Get a schedule

Retrieve a single schedule by ID.

<CodeGroup>
```typescript box.ts
const schedule = await box.schedule.get("schedule_abc123")

console.log(schedule.type)   // "exec" | "prompt"
console.log(schedule.status) // "active" | "paused" | "deleted"
console.log(schedule.cron)
```

```python box.py
schedule = box.schedule.get("schedule_abc123")

print(schedule.type)    # "exec" | "prompt"
print(schedule.status)  # "active" | "paused" | "deleted"
print(schedule.cron)
```
</CodeGroup>

***

### Pause a schedule

Pause an active schedule so it stops running.

<CodeGroup>
```typescript box.ts
await box.schedule.pause("schedule_abc123")
```

```python box.py
box.schedule.pause("schedule_abc123")
```
</CodeGroup>

***

### Resume a schedule

Resume a paused schedule.

<CodeGroup>
```typescript box.ts
await box.schedule.resume("schedule_abc123")
```

```python box.py
box.schedule.resume("schedule_abc123")
```
</CodeGroup>

***

### Delete a schedule

Remove a schedule so it no longer runs.

<CodeGroup>
```typescript box.ts
await box.schedule.delete("schedule_abc123")
```

```python box.py
box.schedule.delete("schedule_abc123")
```
</CodeGroup>

***

## Schedule response

Most schedule methods return a `Schedule` object:

<CodeGroup>
```typescript box.ts
interface Schedule {
  id: string
  box_id: string
  type: "exec" | "prompt"
  cron: string
  command?: string[]         // present when type is "exec"
  prompt?: string            // present when type is "prompt"
  folder?: string
  model?: string
  agent_options?: Record<string, unknown>
  timeout?: number
  status: "active" | "paused" | "deleted"
  webhook_url?: string
  webhook_headers?: Record<string, string>
  last_run_at?: number
  last_run_status?: "completed" | "failed" | "skipped"
  last_run_id?: string
  total_runs: number
  total_failures: number
  created_at: number
  updated_at: number
}
```

```python box.py
# Schedule is a pydantic model; access fields as attributes:
schedule.id              # str
schedule.box_id          # str
schedule.type            # "exec" | "prompt"
schedule.cron            # str
schedule.command         # list[str] | None   (present when type == "exec")
schedule.prompt          # str | None          (present when type == "prompt")
schedule.folder          # str | None
schedule.model           # str | None
schedule.agent_options   # dict | None
schedule.timeout         # float | None
schedule.status          # "active" | "paused" | "deleted"
schedule.webhook_url     # str | None
schedule.webhook_headers # dict | None
schedule.last_run_at     # int | None
schedule.last_run_status # "completed" | "failed" | "skipped" | None
schedule.last_run_id     # str | None
schedule.total_runs      # int
schedule.total_failures  # int
schedule.created_at      # int
schedule.updated_at      # int
```
</CodeGroup>

***

## Cron syntax

Schedules use standard 5-field cron expressions (UTC):

```
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-6, Sun=0)
│ │ │ │ │
* * * * *
```

| Expression | Description |
| --- | --- |
| `* * * * *` | Every minute |
| `0 * * * *` | Every hour |
| `0 9 * * *` | Daily at 9:00 AM |
| `0 9 * * 1-5` | Weekdays at 9:00 AM |
| `*/5 * * * *` | Every 5 minutes |

***

## Examples

### Periodic health check

Schedule a command that checks whether your service is healthy.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({ runtime: "node" })

await box.schedule.exec({
  cron: "*/5 * * * *",
  command: ["bash", "-c", "curl -sf http://localhost:3000/health || echo UNHEALTHY >> /workspace/home/health.log"],
})
```

```python box.py
from upstash_box import Box

box = Box.create(runtime="node")

box.schedule.exec(
    cron="*/5 * * * *",
    command=["bash", "-c", "curl -sf http://localhost:3000/health || echo UNHEALTHY >> /workspace/home/health.log"],
)
```
</CodeGroup>

### Daily code review agent

Schedule an agent to review recent changes every morning.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  agent: {
    harness: Agent.ClaudeCode,
    model: "anthropic/claude-sonnet-4-5",
    apiKey: process.env.ANTHROPIC_API_KEY,
  },
  git: { token: process.env.GITHUB_TOKEN },
})

await box.git.clone({ repo: "github.com/your-org/your-repo" })

await box.schedule.agent({
  cron: "0 9 * * 1-5",
  prompt: "Pull latest changes, review the last 24h of commits, and open issues for any bugs or style violations",
  timeout: 600_000,
  options: { maxBudgetUsd: 2.0, effort: "high" },
})
```

```python box.py
import os
from upstash_box import Box, Agent

box = Box.create(
    runtime="node",
    agent={
        "harness": Agent.CLAUDE_CODE,
        "model": "anthropic/claude-sonnet-4-5",
        "api_key": os.environ["ANTHROPIC_API_KEY"],
    },
    git={"token": os.environ["GITHUB_TOKEN"]},
)

box.git.clone(repo="github.com/your-org/your-repo")

box.schedule.agent(
    cron="0 9 * * 1-5",
    prompt="Pull latest changes, review the last 24h of commits, and open issues for any bugs or style violations",
    timeout=600_000,
    options={"max_budget_usd": 2.0, "effort": "high"},
)
```
</CodeGroup>

### Manage schedules

List active schedules, pause, resume, and clean up ones you no longer need.

<CodeGroup>
```typescript box.ts
const schedules = await box.schedule.list()

for (const s of schedules) {
  console.log(`${s.id} — ${s.type} — ${s.cron} — ${s.status}`)
}

// Pause a schedule
await box.schedule.pause(schedules[0].id)

// Resume it later
await box.schedule.resume(schedules[0].id)

// Remove a specific schedule
await box.schedule.delete(schedules[0].id)
```

```python box.py
schedules = box.schedule.list()

for s in schedules:
    print(f"{s.id} — {s.type} — {s.cron} — {s.status}")

# Pause a schedule
box.schedule.pause(schedules[0].id)

# Resume it later
box.schedule.resume(schedules[0].id)

# Remove a specific schedule
box.schedule.delete(schedules[0].id)
```
</CodeGroup>

# Security & Secrets
Source: https://upstash.com/docs/box/overall/security

## Isolation

Every Upstash Box runs in its own isolated container with a dedicated filesystem, process tree, and network stack. Boxes cannot communicate with or observe each other. Network access is restricted — containers cannot reach private networks, cloud metadata services, or other internal infrastructure.

## Environment Variables

You can pass environment variables when creating a box. These are available to all code running inside the box, including your agent and any user-submitted code.

<CodeGroup>
```typescript box.ts
const box = await Box.create({
  runtime: "node",
  env: {
    DATABASE_URL: "postgres://...",
    ANTHROPIC_API_KEY: "sk-ant-...",
  },
})
```

```python box.py
box = Box.create(
    runtime="node",
    env={
        "DATABASE_URL": "postgres://...",
        "ANTHROPIC_API_KEY": "sk-ant-...",
    },
)
```
</CodeGroup>

<Warning>
Environment variables are visible to all code running inside the box. If you run untrusted code, those secrets can be read by the untrusted code. For sensitive credentials, use [Attach Headers](/docs/box/overall/attach-headers) instead.
</Warning>

## Attach Headers

For injecting secret HTTP headers into outbound HTTPS requests without exposing them inside the container, see [Attach Headers](/docs/box/overall/attach-headers).

## Browser URLs

Boxes created with [`browser: true`](/docs/box/overall/browser/overview) can hand out two kinds of URLs that carry their access token in the URL itself, so they work without an API key:

* [Live view](/docs/box/overall/browser/live-view) URLs are **view-only**. Frames stream out and no input goes in, but anyone with the URL can watch the tab.
* [CDP](/docs/box/overall/browser/connect) URLs give **full control** of the browser to anyone holding them.

Treat both as secrets and share them only where that level of access is intended. Recording playlist URLs are not tokenized. Fetching them requires your Box API key, like any other API call.

## Blocked Environment Variables

For system security, the following environment variables cannot be set:

| Variable | Reason |
|---|---|
| `PATH` | Prevents binary hijacking |
| `HOME` | Prevents home directory manipulation |
| `LD_PRELOAD` | Prevents shared library injection |
| `LD_LIBRARY_PATH` | Prevents library path hijacking |
| `NODE_OPTIONS` | Prevents Node.js flag injection |

All other environment variables — including `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, and their `*_BASE_URL` variants — are allowed. The built-in agent runner uses its own isolated environment that overrides these per-run.

# Shell
Source: https://upstash.com/docs/box/overall/shell

Every box has a full Linux shell and runs on Debian by default.

You can access that shell either through the SDK or directly over SSH.

***

## SSH

Connect to a box with:

```bash
ssh <box-id>@us-east-1.box.upstash.com
```

When prompted for a password, use your **Box API key**.

For example:

```bash
ssh current-wasp-05510@us-east-1.box.upstash.com
```

<Tip>
  You can also copy this command from the **SSH** button on the box details page in the Upstash Console.
</Tip>

***

## API

### Run a shell command

A shell command resolves when the command finishes and gives you back the result and status.

<CodeGroup>
```typescript box.ts
const run = await box.exec.command("echo hello")

console.log(run.result) // "hello"
console.log(run.status) // "completed"
```

```python box.py
run = box.exec.command("echo hello")

print(run.result)  # "hello"
print(run.status)  # "completed"
```
</CodeGroup>

If you need additional system tools, install them with Debian's package manager inside the box.

<CodeGroup>
```typescript box.ts
await box.exec.command("sudo apt-get install <package>")
```

```python box.py
box.exec.command("sudo apt-get install <package>")
```
</CodeGroup>

***

### Run a code snippet

You can execute code inside of a box:

<CodeGroup>
```typescript box.ts
const run = await box.exec.code({
  code: "console.log('hello')",
  lang: "js",
  timeout: 10_000,
})

console.log(run.output) // "hello"
console.log(run.exit_code) // "0"
```

```python box.py
run = box.exec.code(code="print('hello')", lang="python", timeout=10_000)

print(run.result)     # "hello"
print(run.exit_code)  # 0
```
</CodeGroup>

***

### Check exit status

If the command fails, the run status will be `"failed"` and its result contains the stderr output.

<CodeGroup>
```typescript box.ts
const run = await box.exec.command("node -e 'process.exit(1)'")

console.log(run.status) // "failed"
```

```python box.py
run = box.exec.command("node -e 'process.exit(1)'")

print(run.status)  # "failed"
```
</CodeGroup>

***

### Chain commands

Pass a full shell expression. Pipes, redirects, and chained commands all work as expected.

<CodeGroup>
```typescript box.ts
const run = await box.exec.command("cd /work && npm install && npm test")

console.log(run.result)
```

```python box.py
run = box.exec.command("cd /work && npm install && npm test")

print(run.result)
```
</CodeGroup>

***

### Cancel a long-running command

You can cancel a run to abort it. The status becomes `"cancelled"`.

<CodeGroup>
```typescript box.ts
const run = await box.exec.command("sleep 10")
await run.cancel()

console.log(run.status) // "cancelled"
```

```python box.py
run = box.exec.command("sleep 10")
run.cancel()

print(run.status)  # "cancelled"
```
</CodeGroup>

***

### Retrieve logs

After a command finishes, call `logs()` to get the full timestamped output for debugging or auditing.

<CodeGroup>
```typescript box.ts
const run = await box.exec.command("npm test")
const logs = await run.logs()

console.log(logs)
// [
//   { timestamp: "2026-02-23T...", level: "info", message: "PASS src/auth.test.ts" },
//   { timestamp: "2026-02-23T...", level: "info", message: "Tests: 12 passed, 12 total" },
// ]
```

```python box.py
run = box.exec.command("npm test")
logs = run.logs()

print(logs)
# [
#   RunLog(timestamp="2026-02-23T...", level="info", message="PASS src/auth.test.ts"),
#   RunLog(timestamp="2026-02-23T...", level="info", message="Tests: 12 passed, 12 total"),
# ]
```
</CodeGroup>

***

## Examples

### Install dependencies before a run

Set up the environment with `box.exec.command()`, then hand off to the agent.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  agent: { harness: Agent.ClaudeCode, model: "anthropic/claude-opus-4-6", apiKey: process.env.ANTHROPIC_API_KEY },
  git: { token: process.env.GITHUB_TOKEN },
})

await box.git.clone({ repo: "github.com/your-org/your-api" })
await box.exec.command("npm install")

await box.agent.run({
  prompt: "Run the test suite and fix any failing tests",
})
```

```python box.py
import os
from upstash_box import Box, Agent

box = Box.create(
    runtime="node",
    agent={"harness": Agent.CLAUDE_CODE, "model": "anthropic/claude-opus-4-6", "api_key": os.environ["ANTHROPIC_API_KEY"]},
    git={"token": os.environ["GITHUB_TOKEN"]},
)

box.git.clone(repo="github.com/your-org/your-api")
box.exec.command("npm install")

box.agent.run(prompt="Run the test suite and fix any failing tests")
```
</CodeGroup>

### Run a build and extract artifacts

Execute a build command, then download the output.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({ runtime: "node" })

await box.files.upload([
  { path: "./src", destination: "/work/src" },
  { path: "./package.json", destination: "/work/package.json" },
])

await box.exec.command("cd /work && npm install && npm run build")
await box.files.download({ folder: "/work/dist" })
await box.delete()
```

```python box.py
from upstash_box import Box

box = Box.create(runtime="node")

box.files.upload([
    {"path": "./src", "destination": "/work/src"},
    {"path": "./package.json", "destination": "/work/package.json"},
])

box.exec.command("cd /work && npm install && npm run build")
box.files.download(folder="/work/dist")
box.delete()
```
</CodeGroup>

### Health-check before handing off

Verify the environment is usable before starting a longer agent task.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "python",
  agent: { harness: Agent.ClaudeCode, model: "anthropic/claude-opus-4-6", apiKey: process.env.ANTHROPIC_API_KEY },
})

const check = await box.exec.command("python3 --version && pip list")
console.log(check.result)

await box.agent.run({
  prompt: "Write a data pipeline that reads /work/raw.csv, cleans it, and saves /work/clean.parquet",
})
```

```python box.py
import os
from upstash_box import Box, Agent

box = Box.create(
    runtime="python",
    agent={"harness": Agent.CLAUDE_CODE, "model": "anthropic/claude-opus-4-6", "api_key": os.environ["ANTHROPIC_API_KEY"]},
)

check = box.exec.command("python3 --version && pip list")
print(check.result)

box.agent.run(prompt="Write a data pipeline that reads /work/raw.csv, cleans it, and saves /work/clean.parquet")
```
</CodeGroup>

# Snapshots
Source: https://upstash.com/docs/box/overall/snapshots

Snapshots let you capture and duplicate box state. Use **snapshots** to save a point-in-time checkpoint you can restore later.

***

## What's included

A snapshot captures:

* **Filesystem**: the full disk state of the box at the time of the snapshot.
* **Agent configuration**: the agent harness, model, and API key settings.

A snapshot does not carry active schedules over to the new box.

<Note>
Snapshots are the best way to preserve cloned repositories, installed dependencies, and prepared workspaces. If your box already has a repo checked out, restoring from a snapshot is usually faster than cloning it again.
</Note>

***

## API

### Create a snapshot

Call `snapshot()` on a running or paused box. The returned `Snapshot` object contains the ID you need to restore later.

<CodeGroup>
```typescript box.ts
const snapshot = await box.snapshot({ name: "after-setup" })

console.log(snapshot)
// {
//   id: "snap_x7f...",
//   name: "after-setup",
//   boxId: "box_abc123",
//   sizeBytes: 52428800,
//   createdAt: "2026-02-23T..."
// }
```

```python box.py
snapshot = box.snapshot(name="after-setup")

print(snapshot)
# Snapshot(
#   id="snap_x7f...",
#   name="after-setup",
#   box_id="box_abc123",
#   size_bytes=52428800,
#   status="ready",
#   created_at=1771845600,
# )
```
</CodeGroup>

<Note>
Snapshots are independent of the source box. Deleting the original box does not delete its snapshots. They remain available for restore at any time.
</Note>

***

### List snapshots

Retrieve all snapshots belonging to a box.

<CodeGroup>
```typescript box.ts
const snapshots = await box.listSnapshots()

console.log(snapshots)
// [
//   { id: "snap_x7f...", name: "after-setup", status: "ready", ... },
//   { id: "snap_a3b...", name: "pre-refactor", status: "ready", ... },
// ]
```

```python box.py
snapshots = box.list_snapshots()

print(snapshots)
# [
#   Snapshot(id="snap_x7f...", name="after-setup", status="ready", ...),
#   Snapshot(id="snap_a3b...", name="pre-refactor", status="ready", ...),
# ]
```
</CodeGroup>

***

### Delete a snapshot

Remove a snapshot by ID.

<CodeGroup>
```typescript box.ts
await box.deleteSnapshot("snap_x7f...")
```

```python box.py
box.delete_snapshot("snap_x7f...")
```
</CodeGroup>

***

### Restore from a snapshot

`Box.fromSnapshot()` provisions a new box with the exact state from the snapshot. The original box is unaffected. You can branch into multiple boxes from the same checkpoint.

<CodeGroup>
```typescript box.ts
const restored = await Box.fromSnapshot(snapshot.id)

// Box.fromSnapshot accepts creation options such as name:
const named = await Box.fromSnapshot(snapshot.id, { name: "restored-worker" })

const files = await restored.files.list("/work")
console.log(files)
```

```python box.py
restored = Box.from_snapshot(snapshot.id)

# Box.from_snapshot accepts creation options such as name:
named = Box.from_snapshot(snapshot.id, name="restored-worker")

files = restored.files.list("/work")
print(files)
```
</CodeGroup>

The restored box starts with the same workspace state from the snapshot. You can also configure lifecycle settings such as keep-alive and init commands on the restored box.

***

## Examples

### Checkpoint before risky operations

Snapshot your working state, then let the agent attempt a large refactor. If the result is bad, we restore the original state and try a different prompt.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const box = await Box.create({
  runtime: "node",
  agent: { harness: Agent.ClaudeCode, model: "anthropic/claude-opus-4-6", apiKey: process.env.ANTHROPIC_API_KEY },
  git: { token: process.env.GITHUB_TOKEN },
})

await box.git.clone({ repo: "github.com/my-org/my-repo" })
const checkpoint = await box.snapshot({ name: "pre-refactor" })

const run = await box.agent.run({
  // 👇 Potentially difficult task
  prompt: "Rewrite the database layer to use connection pooling",
})

if (run.status === "failed") {
  const fallback = await Box.fromSnapshot(checkpoint.id)
  await fallback.agent.run({
    prompt: "Add connection pooling to the existing database layer without rewriting it",
  })
}
```

```python box.py
import os
from upstash_box import Box, Agent

box = Box.create(
    runtime="node",
    agent={"harness": Agent.CLAUDE_CODE, "model": "anthropic/claude-opus-4-6", "api_key": os.environ["ANTHROPIC_API_KEY"]},
    git={"token": os.environ["GITHUB_TOKEN"]},
)

box.git.clone(repo="github.com/my-org/my-repo")
checkpoint = box.snapshot(name="pre-refactor")

run = box.agent.run(
    # 👇 Potentially difficult task
    prompt="Rewrite the database layer to use connection pooling",
)

if run.status == "failed":
    fallback = Box.from_snapshot(checkpoint.id)
    fallback.agent.run(
        prompt="Add connection pooling to the existing database layer without rewriting it",
    )
```
</CodeGroup>

### Reusable base environments

Install dependencies once, snapshot, then spawn boxes from the snapshot to skip setup time.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const base = await Box.create({ runtime: "node" })
await base.exec.command("npm install -g typescript eslint prettier")
const baseSnap = await base.snapshot({ name: "node-toolchain" })
await base.delete()

const boxes = await Promise.all(
  tasks.map(async (task) => {
    const box = await Box.fromSnapshot(baseSnap.id)
    await box.agent.run({ prompt: task })
    return box
  })
)
```

```python box.py
import asyncio
from upstash_box import AsyncBox

async def main() -> None:
    base = await AsyncBox.create(runtime="node")
    await base.exec.command("npm install -g typescript eslint prettier")
    base_snap = await base.snapshot(name="node-toolchain")
    await base.delete()

    async def run_task(task: str) -> AsyncBox:
        box = await AsyncBox.from_snapshot(base_snap.id)
        await box.agent.run(prompt=task)
        return box

    # spawn boxes from the snapshot in parallel
    await asyncio.gather(*(run_task(t) for t in tasks))

asyncio.run(main())
```
</CodeGroup>

### Fan-out from a single state

Clone a repo once, snapshot, then run different agents or prompts in parallel from the same starting point.

<CodeGroup>
```typescript box.ts
import { Agent, Box } from "@upstash/box"

const seed = await Box.create({
  runtime: "node",
  git: { token: process.env.GITHUB_TOKEN },
})
await seed.git.clone({ repo: "github.com/your-org/monorepo" })
const snap = await seed.snapshot({ name: "repo-cloned" })
await seed.delete()

const [security, performance, docs] = await Promise.all([
  Box.fromSnapshot(snap.id),
  Box.fromSnapshot(snap.id),
  Box.fromSnapshot(snap.id),
])

await Promise.all([
  security.agent.run({ prompt: "Audit src/ for SQL injection vulnerabilities" }),
  performance.agent.run({ prompt: "Profile the hot paths in src/api/ and optimize" }),
  docs.agent.run({ prompt: "Generate API documentation for all public exports" }),
])
```

```python box.py
import asyncio
import os
from upstash_box import AsyncBox

async def main() -> None:
    seed = await AsyncBox.create(
        runtime="node",
        git={"token": os.environ["GITHUB_TOKEN"]},
    )
    await seed.git.clone(repo="github.com/your-org/monorepo")
    snap = await seed.snapshot(name="repo-cloned")
    await seed.delete()

    security, performance, docs = await asyncio.gather(
        AsyncBox.from_snapshot(snap.id),
        AsyncBox.from_snapshot(snap.id),
        AsyncBox.from_snapshot(snap.id),
    )

    await asyncio.gather(
        security.agent.run(prompt="Audit src/ for SQL injection vulnerabilities"),
        performance.agent.run(prompt="Profile the hot paths in src/api/ and optimize"),
        docs.agent.run(prompt="Generate API documentation for all public exports"),
    )

asyncio.run(main())
```
</CodeGroup>

# Use Cases
Source: https://upstash.com/docs/box/overall/use-cases

The idea behind Upstash Box is simple: **give AI its own computer**. Your agent gets a full, isolated cloud environment it can control. Run commands, write files, or execute code independent of any user device. Freeze a box anytime, and continue days or even weeks later with perfect resumability.

***

## 1. Agent Servers

A very powerful pattern is the **Agent Server**: a long-running, per-tenant agent that persists its state across sessions. Unlike ephemeral sandboxes that lose everything on shutdown, an Agent Server keeps its history, context, and learned preferences intact forever.

  <img />

Each user gets a dedicated Box running its own agent. The agent observes every request and response in a non-blocking way. It builds up a personalized understanding of what that user needs. Over time, it contributes back to a shared **Knowledge Base**, so insights from one tenant can improve results for everyone.

Because boxes are serverless, idle tenants only cost a very low storage rate. When a user returns, their box wakes instantly with all prior state intact (installed packages, file history, learned preferences) and picks up exactly where it left off.

## 2. Multi-Agent Orchestration

Box's async SDK lets you spin up multiple boxes in parallel, each running a specialized agent with a distinct role. Once every agent finishes, a final box can synthesize their outputs into a single result.

  <img />

A practical example is an automated **PR review pipeline**. When a pull request is opened, you fan out to three boxes. One for security review, one for code quality, and one for architecture. Then you collect their findings in a fourth box that summarizes everything and posts a comment on GitHub.

<CodeGroup>
```typescript box.ts
const pr = "https://github.com/acme/app/pull/42"

const [security, quality, architecture] = await Promise.all([
  Box.create({ runtime: "node", agent: { harness: Agent.ClaudeCode, model: "anthropic/claude-sonnet-4-6" } }),
  Box.create({ runtime: "node", agent: { harness: Agent.ClaudeCode, model: "anthropic/claude-sonnet-4-6" } }),
  Box.create({ runtime: "node", agent: { harness: Agent.ClaudeCode, model: "anthropic/claude-sonnet-4-6" } }),
])

const reviews = await Promise.all([
  security.agent.run({ prompt: `Security review for ${pr}` }),
  quality.agent.run({ prompt: `Code quality review for ${pr}` }),
  architecture.agent.run({ prompt: `Architecture review for ${pr}` }),
])

const jury = await Box.create({
  runtime: "node",
  agent: { harness: Agent.ClaudeCode, model: "anthropic/claude-sonnet-4-6" },
  git: { token: process.env.GIT_TOKEN },
})

await jury.agent.run({
  prompt: `Summarize these reviews and post a comment on ${pr}:\n${reviews.map((r) => r.result).join("\n\n")}`,
})
```

```python box.py
import asyncio
import os
from upstash_box import AsyncBox, Agent

async def main():
    pr = "https://github.com/acme/app/pull/42"

    security, quality, architecture = await asyncio.gather(
        AsyncBox.create(runtime="node", agent={"harness": Agent.CLAUDE_CODE, "model": "anthropic/claude-sonnet-4-6"}),
        AsyncBox.create(runtime="node", agent={"harness": Agent.CLAUDE_CODE, "model": "anthropic/claude-sonnet-4-6"}),
        AsyncBox.create(runtime="node", agent={"harness": Agent.CLAUDE_CODE, "model": "anthropic/claude-sonnet-4-6"}),
    )

    reviews = await asyncio.gather(
        security.agent.run(prompt=f"Security review for {pr}"),
        quality.agent.run(prompt=f"Code quality review for {pr}"),
        architecture.agent.run(prompt=f"Architecture review for {pr}"),
    )

    jury = await AsyncBox.create(
        runtime="node",
        agent={"harness": Agent.CLAUDE_CODE, "model": "anthropic/claude-sonnet-4-6"},
        git={"token": os.environ["GITHUB_TOKEN"]},
    )

    summary = "\n\n".join(r.result for r in reviews)
    await jury.agent.run(
        prompt=f"Summarize these reviews and post a comment on {pr}:\n{summary}",
    )

asyncio.run(main())
```
</CodeGroup>

Because each box is isolated, the agents cannot interfere with each other. You get true parallelism with independent filesystems, and the orchestration logic stays in your own code.

## 3. Parallel Testing & Comparison

Box makes it easy to run parallel test scenarios at scale. Spin up N boxes, each running a different model against the same inputs, and compare the results side by side.

  <img />

For example, at Context7 we use Box to benchmark LLMs for context extraction over documentation. We spin up boxes in parallel, each running a different model against the same documentation files and prompts. We then evaluate hallucination percentage, accuracy score, and context quality to find the best model:

<CodeGroup>
```typescript box.ts
const models = ["anthropic/claude-opus-4-6", "openai/gpt-5.3-codex", "openrouter/google/gemini-2.5-pro"]

const docs = await fs.readFile("./documentation.md", "utf-8")
const prompt = `Extract all API endpoints from this documentation:\n${docs}`

const boxes = await Promise.all(
  models.map((model) => Box.create({ runtime: "node", agent: { model } })),
)

const results = await Promise.all(boxes.map((box) => box.agent.run({ prompt })))

const evaluation = results.map((r, i) => ({
  model: models[i],
  result: r.result,
  hallucinationPct: evaluateHallucination(r.result, docs),
  accuracyScore: evaluateAccuracy(r.result, docs),
}))

const bestModel = evaluation.sort((a, b) => a.hallucinationPct - b.hallucinationPct)[0]
```

```python box.py
import asyncio
from upstash_box import AsyncBox, Agent

async def main():
    # each model is paired with the harness that runs it
    agents = [
        {"harness": Agent.CLAUDE_CODE, "model": "anthropic/claude-opus-4-6"},
        {"harness": Agent.CODEX, "model": "openai/gpt-5.3-codex"},
        {"harness": Agent.OPEN_CODE, "model": "openrouter/google/gemini-2.5-pro"},
    ]

    with open("./documentation.md", encoding="utf-8") as f:
        docs = f.read()
    prompt = f"Extract all API endpoints from this documentation:\n{docs}"

    boxes = await asyncio.gather(
        *(AsyncBox.create(runtime="node", agent=agent) for agent in agents)
    )

    results = await asyncio.gather(*(box.agent.run(prompt=prompt) for box in boxes))

    evaluation = [
        {
            "model": agents[i]["model"],
            "result": r.result,
            "hallucination_pct": evaluate_hallucination(r.result, docs),
            "accuracy_score": evaluate_accuracy(r.result, docs),
        }
        for i, r in enumerate(results)
    ]

    best_model = min(evaluation, key=lambda e: e["hallucination_pct"])

asyncio.run(main())
```
</CodeGroup>

Each box is fully isolated, so one model's behavior never leaks into another's results.

# Add a Payment Method
Source: https://upstash.com/docs/common/account/add-payment-method

Upstash does not require a credit card for Free databases. For paid databases, you need to add at least one payment method. To add one:

1. Sign in to the [Upstash Console](https://console.upstash.com).
2. Click your profile in the top right and select `Account` from the dropdown.
3. Open the `Billing` tab.
4. Click `Add Your Card`.
5. Enter your name and card information in the form that appears:

  <img />

You can save multiple cards and pick one as the default. Payments are charged from the default card.

  <img />

## Payment Security

Upstash does not store credit card information on its servers. Payments are processed by [Stripe](https://stripe.com/docs/security/stripe).

# Audit Logs
Source: https://upstash.com/docs/common/account/auditlogs

Audit logs give you a chronological set of activity records that have affected
your databases and Upstash account. You can see the list of all activities on a
single page. You can access your audit logs under `Account > Audit Logs` in your
console:

  <img />

Here the `Source` column shows if the action has been called by the console or via
an API key. The `Entity` column gives you the name of the resource that has been
affected by the action. For example, when you delete a database, the name of the
database will be shown here. Also, you can see the IP address which performed the
action.

## Security

You can track your audit logs to detect any unusual activity on your account and
databases. When you suspect any security breach, you should delete the API key
related to suspicious activity and inform us by emailing
[support@upstash.com](mailto:support@upstash.com)

## Retention period

After the retention period, the audit logs are deleted. The retention period for free databases is 7 days, for pay-as-you-go databases, it is 30 days, and for the Pro tier, it is one year.

# AWS Marketplace
Source: https://upstash.com/docs/common/account/awsmarketplace

<Check >
**Prerequisite**

You need an Upstash account before subscribing on AWS, create one
[here](https://console.upstash.com).

</Check>

Upstash is available on the AWS Marketplace, which is particularly beneficial for users who already get other services from AWS Marketplace and can consolidate Upstash under a single bill.

You can search "Upstash" on AWS Marketplace or just click [here](https://aws.amazon.com/marketplace/pp/prodview-fssqvkdcpycco).

  <img />

Once you click subscribe, you will be prompted to select which personal or team account you wish to link with your AWS Subscription.

  <img />

Once your account is linked, regardless of which Upstash product you use, all of your usage will be billed to your AWS Account. You can also upgrade or downgrade your subscription through Upstash console.

  <img />

# Cost Explorer
Source: https://upstash.com/docs/common/account/costexplorer

The Cost Explorer pages allow you to view your current and previous months’ costs. To access the Cost Explorer, navigate to the left menu and select Account > Cost Explorer. Below is an example report:

  <img />

You can select a specific month to view the cost breakdown for that period. Here's the explanation of the fields in the report:

**Request:** This represents the total number of requests sent to the database.

**Storage:** This indicates the average size of the total storage consumed. Upstash database includes a persistence layer for data durability. For example, if you have 1 GB of data in your database throughout the entire month, this value will be 1 GB. Even if your database is empty for the first 29 days of the month and then expands to 30 GB on the last day, this value will still be 1 GB.

**Cost:** This field represents the total cost of your database in US Dollars.

> The values for the current month is updated hourly, so values can be stale up
> to 1 hour.

# Create an Account
Source: https://upstash.com/docs/common/account/createaccount

You can sign up for <a href="https://console.upstash.com" target="_blank">Upstash</a> using your Amazon, Github or Google accounts. Alternatively, if you prefer not to use these authentication providers or want to sign up with a corporate email address, you can also sign up using email and password.

<Note>

We do not access your information other than:

* Your email
* Your name
* Your profile picture and we never share your information with third parties.

</Note>

# Developer API
Source: https://upstash.com/docs/common/account/developerapi

Using Upstash API, you can develop applications that can create and manage
Upstash databases and Upstash Vector Indexes. You can automate everything that
you can do in the console. To use developer API, you need to create an API key
in the console.

Note: The Developer API is only available to native Upstash accounts. Accounts created via third-party platforms like Vercel or Fly.io are not supported.

See [DevOps](/docs/devops) for details.

# Account and Billing FAQ
Source: https://upstash.com/docs/common/account/faq

## How can I delete my account?
You can delete your account from `Account` > `Settings` > `Delete Account`. You should first delete all your databases and clusters. After you delete your account, all your data and payment information will be deleted and you will not be able to recover it.

## How can I delete my credit card?
You can delete your credit card from `Account` > `Billing` page. However, you should first add a new credit card to be able to delete the existing one. If you want to delete all of your payment information, you should delete your account.

## How can I change my email address?
You can change your account e-mail address in `Account` > `Settings` page. In order to change your billing e-mail adress, please see `Account` > `Billing` page. If you encounter any issues, please contact us at [support@upstash.com](mailto:support@upstash.com) to change your email address.

## Can I set an upper spending limit, so I don't get surprises after an unexpected amount of high traffic?
On Pay as You Go model, you can set a budget for your Redis instances. When your monthly cost reaches the max budget, we send an email to inform you and throttle your instance. You will not be charged beyond your set budget.

To set the budget, you can go to the "Usage" tab of your Redis instance and click "Change Budget" under the cost metric.

## What happens if my payment fails?

If a payment failure occurs, we will retry the payment three more times before suspending the account. During this time, you will receive email notifications about the payment failure. If the account is suspended, all resources in the account will be inaccessible. If you add a valid payment method after the account suspension, your account will be automatically unsuspended during the next payment attempt.

## What happens if I unsubscribe from AWS Marketplace but I don't have any other payment methods?

We send a warning email three times before suspending an account. If no valid payment method is added, we suspend the account. Once the account is suspended, all resources within the account will be inaccessible. If you add a valid payment method after the account suspension, your account will be automatically unsuspended during the next system check.

## I have a question about my bill, who should I contact?
Please contact us at [support@upstash.com](mailto:support@upstash.com).

# Payment History
Source: https://upstash.com/docs/common/account/paymenthistory

The Payment History page gives you information about your payments. You can open your
payment history in the left menu under Account > Payment History. Here an example
report:

  <img />

You can download receipt. If one of your payments failed, you can retry your
payment on this page.

# Teams and Users
Source: https://upstash.com/docs/common/account/teams

Team management enables collaboration with other users. You can create a team and invite people to join by using their email addresses. Team members will have access to databases created under the team based on their assigned roles.

  <img />

## Create Team

You can create a team using the menu `Account > Teams`

  <img />
<br />
  <img />

> A user can create up to 5 teams. You can be part of even more teams but only
> be the owner of 5 teams. If you need to own more teams please email us at
> support@upstash.com.

You can still continue using your personal account or switch to a team.

> The databases in your personal account are not shared with anyone. If you want
> your database to be accessible by other users, you need to create it under a
> team.

## Switch Team

You need to switch to the team to create databases shared with other team
members. You can switch to the team via the switch button in the team table. Or
you can click your profile pic in the top right and switch to any team listed
there.

  <img />

## Add/Remove Team Member

After switching to a team, if you are the Owner or an Admin of the team, you can add team members by navigating to `Account > Teams`. Enter their email address and select a role, and we will send an invitation email to that address. The user joins the team by clicking the acceptance link in the email. It's not an issue if the email address is not yet registered with Upstash — they can accept the invitation after registering with that email.

Until the invitation is accepted, the user appears as a **pending member** and does not have access to the team yet. You can revoke a pending invitation (**Revoke Invite**) or remove an existing member from the same page.

> Only Admins or the Owner can add/remove users.

  <img />

## Roles

While adding a team member, you will need to select a role. Here are the access rights associated with each role:

* Admin: This role has full access, including the ability to add and remove members, manage databases, and payment methods.

* Dev: This role can create, manage, and delete databases but cannot manage users or payment methods.

* Finance: This role is limited to managing payment methods and cannot manage databases or users.

* Owner: The Owner role has all the access rights of an Admin and, in addition to having the ability to delete the team. This role is automatically assigned to the user who created the team, and you cannot assign it to other members.

> If you want to change a user's role, you will need to delete and re-add them with the desired access rights.

## Delete Team

Only the original creator (owner) can delete a team. Also the team should not
have any active databases, namely all databases under the team should be deleted
first. To delete your team, first you need to switch your personal account then
you can delete your team in the team list under `Account > Teams`.

  <img />

# Access Anywhere
Source: https://upstash.com/docs/common/concepts/access-anywhere

Upstash has integrated REST APIs into all its products to facilitate access from various runtime environments. This integration is particularly beneficial for edge runtimes like Cloudflare Workers and Vercel Edge, which do not permit TCP connections, and for serverless functions such as AWS Lambda, which are stateless and do not retain connection information between invocations.

### Rationale
The absence of TCP connection support in edge runtimes and the stateless nature of serverless functions necessitate a different approach for persistent connections typically used in traditional server setups. The stateless REST API provided by Upstash addresses this gap, enabling consistent and reliable communication with data stores from these platforms.

### REST API Design
The REST APIs for Upstash services are thoughtfully designed to align closely with the conventions of each product. This ensures that users who are already familiar with these services will find the interactions intuitive and familiar. Our API endpoints are self-explanatory, following standard REST practices to guarantee ease of use and seamless integration.

<img />

### SDKs for Popular Languages
To enhance the developer experience, Upstash is developing SDKs in various popular programming languages. These SDKs simplify the process of integrating Upstash services with your applications by providing straightforward methods and functions that abstract the underlying REST API calls.

### Resources
[Redis REST API Docs](/docs/redis/features/restapi)

[QStash REST API Docs](/docs/qstash/api/authentication)

[Redis SDK - Typescript](https://github.com/upstash/upstash-redis)

[Redis SDK - Python](https://github.com/upstash/redis-python)

[QStash SDK - Typescript](https://github.com/upstash/sdk-qstash-ts)

# Global Replication
Source: https://upstash.com/docs/common/concepts/global-replication

Upstash Redis automatically replicates your data to the regions you choose, so your application stays fast and responsive-no matter where your users are.

Add or remove regions from a database at any time with zero downtime. Each region acts as a replica, holding a copy of your data for low latency and high availability.

***

## Built for Modern Serverless Architectures

In serverless computing, performance isn't just about fast code—it's also about fast, reliable data access from anywhere in the world. Whether you're using Vercel Functions, Cloudflare Workers, Fastly Compute, or Deno Deploy, your data layer needs to be as distributed and flexible as your compute for best performance.

Upstash Global replicates your Redis data across multiple regions to:

* Minimize round-trip latency
* Guarantee high availability at scale

...even under heavy or dynamic workloads. Our HTTP-based Redis® client is optimized for serverless environments and delivers consistent performance under high concurrency or variable workloads.

As serverless platforms evolve with features like in-function concurrency (e.g. [Vercel's Fluid Compute](https://vercel.com/fluid)), you need a data layer that can keep up. Upstash Redis is a globally distributed, low-latency database that scales with your compute, wherever it runs.

***

## How Global Replication Works

To minimize latency for read operations, we use a replica model. Our tests show sub-millisecond latency for read commands in the same AWS region as the Upstash Redis® instance.

**Read commands are automatically served from the geographically closest replica**:

  <img />

**Write commands go to the primary database** for consistency. After a successful write, they are replicated to all read replicas:

  <img />

***

## Available Regions

To create a globally distributed database, select a primary region and the number of read regions:

* Select a primary region for most write operations for best performance.
* Select read regions close to your users for optimized read speeds.

Each request is then automatically served by the closest read replica for maximum performance and minimum latency:

  <img />

**You can create read replicas in the following regions:**

* AWS US-East-1 (North Virginia)
* AWS US-East-2 (Ohio)
* AWS US-West-1 (North California)
* AWS US-West-2 (Oregon)
* AWS EU-West-1 (Ireland)
* AWS EU-West-2 (London)
* AWS EU-Central-1 (Frankfurt)
* AWS AP-South-1 (Mumbai)
* AWS AP-Northeast-1 (Tokyo)
* AWS AP-Southeast-1 (Singapore)
* AWS AP-Southeast-2 (Sydney)
* AWS SA-East-1 (São Paulo)

Check out [our blog post](https://upstash.com/blog/global-database) to learn more about our global replication philosophy. You can also explore our [live benchmark](https://latency.upstash.com/) to see Upstash Redis latency from different locations around the world.

# Scale to Zero
Source: https://upstash.com/docs/common/concepts/scale-to-zero

Traditionally, cloud services required users to predict their resource needs and provision servers or instances based on those predictions. This often led to over-provisioning to handle potential peak loads, resulting in paying for unused resources during periods of low demand.

By _scaling to zero_, our pricing model aligns more closely with actual usage.

## Pay for usage

You're only charged for the resources you actively use. When your application experiences low activity or no incoming requests, the system automatically scales down resources to a minimal level. This means you're no longer paying for idle capacity, resulting in cost savings.

<img />

## Flexibility
"Scaling to zero" offers flexibility in scaling both up and down. As your application experiences traffic spikes, the system scales up resources to meet demand. Conversely, during quiet periods, resources scale down.

## Focus on Innovation

Developers can concentrate on building and improving the application without constantly worrying about resource optimization. Upstash handles the scaling, allowing developers to focus on creating features that enhance user experiences.

In essence, this aligns pricing with actual utilization, increases cost efficiency, and promotes a more sustainable approach to resource consumption. This model empowers businesses to leverage cloud resources without incurring unnecessary expenses, making cloud computing more accessible and attractive to a broader range of organizations.

# Serverless
Source: https://upstash.com/docs/common/concepts/serverless

Upstash is a modern serverless data platform. But what do we mean by serverless?

## No Server Management

In a serverless setup, developers don't need to worry about configuring or managing servers. We take care of server provisioning, scaling, and maintenance.

## Automatic Scaling

As traffic or demand increases, Upstash automatically scales the required resources to handle the load. This means applications can handle sudden spikes in traffic without manual intervention.

## Granular Billing

We charge based on the actual usage of resources rather than pre-allocated capacity. This can lead to more cost-effective solutions, as users only pay for what they consume. [Read more](/docs/common/concepts/scale-to-zero)

## Stateless Functions

In serverless architectures, functions are typically stateless. However, the traditional approach involves establishing long-lived connections to databases, which can lead to issues in serverless environments if connections aren't properly managed after use. Additionally, there are scenarios where TCP connections may not be feasible. Upstash addresses this issue by offering access via HTTP, a universally available protocol across all platforms.

## Rapid Deployment

Fast iteration is the key to success in today's competitive environment. You can create a new Upstash database in seconds, with minimal required configuration.

# Account & Teams
Source: https://upstash.com/docs/common/help/account

## Create an Account

You can sign up to <a href="https://console.upstash.com" target="_blank">Upstash</a> using your Amazon, Github or Google accounts. Alternatively you can sign up using
email/password registration if you don't want to use these auth providers, or you
want to sign up using a corporate email address.

<Note>
    We do not access your information other than:
    * Your email
    * Your name
    * Your profile picture and we never share your information with third parties.
</Note>

Team management allows you collaborate with other users. You can create a team
and invite people to the team by email addresses. The team members will have
access to the databases created under the team depending on their roles.

    <img />

## Teams
### Create Team

You can create a team using the menu `Account > Teams`

    <img />
<br />
    <img />

> A user can create up to 5 teams. You can be part of even more teams but only
> be the owner of 5 teams. If you need to own more teams please email us at
> support@upstash.com.

You can still continue using your personal account or switch to a team.

> The databases in your personal account are not shared with anyone. If you want
> your database to be accessible by other users, you need to create it under a
> team.

### Switch Team

You need to switch to the team to create databases shared with other team
members. You can switch to the team via the switch button in the team table. Or
you can click your profile pic in the top right and switch to any team listed
there.

    <img />

### Add/Remove Team Member

Once you switched to a team, you can add team members in `Account > Teams` if
you are Owner or Admin for of the team. Entering email will be enough. The email
may not registered to Upstash yet, it is not a problem. Once the user registers
with that email, he/she will be able to switch to the team. We do not send
invitation, so when you add a member, he/she becomes a member directly. You can
remove the members from the same page.

> Only Admins or the Owner can add/remove users.

    <img />

### Roles

While adding a team member you need to select a role. Here the privileges of
each role:

* Admin: This role has full access including adding removing members, databases,
payment methods.

* Dev: This role can create, manage and delete databases. It can not manage
users and payment methods.

* Finance: This role can only manage payment methods. It can not manage the
databases and users.

* Owner: Owner has all the privileges that admin has. In addition he is the only
person who can delete the team. This role is assigned to the user who created
the team. So you can not create a member with Owner role.

> If you want change role of a user, you need to delete and add again.

### Delete Team

Only the original creator (owner) can delete a team. Also the team should not
have any active databases, namely all databases under the team should be deleted
first. To delete your team, first you need to switch your personal account then
you can delete your team in the team list under `Account > Teams`.

    <img />

# Announcements
Source: https://upstash.com/docs/common/help/announcements

Removal of GraphQL API and edge caching (Redis) (October 1, 2022) These two
features have been already deprecated. We are planning to deactivate them
completely on November 1st. We recommend use of REST API to replace GraphQL API
and Global databases instead of Edge caching.

Removal of strong consistency (Redis) (October 1, 2022) Upstash supported Strong
Consistency mode for the single region databases. We decided to deprecate this
feature because its effect on latency started to conflict with the performance
expectations of Redis use cases. Moreover, we improved the consistency of
replication to guarantee Read-Your-Writes consistency. Strong consistency will
be disabled on existing databases on November 1st.

#### Redis pay-as-you-go usage cap (October 1, 2022)

We are increasing the max usage cap to \$160 from \$120 as of October 1st. This
update is needed because of the increasing infrastructure cost due to
replicating all databases to multiple instances. After your database exceeds the
max usage cost, your database might be rate limited.

#### Replication is enabled (Sep 29, 2022)

All new and existing paid databases will be replicated to multiple replicas.
Replication enables high availability in case of system and infrastructure
failures. Starting from October 1st, we will gradually upgrade all databases
without downtime. Free databases will stay single replica.

<hr />

#### QStash Price Decrease (Sep 15, 2022)

The price is \$1 per 100K requests.

<hr />

#### [Pulumi Provider is available](https://upstash.com/blog/upstash-pulumi-provider)  (August 4, 2022)

<hr />

#### [QStash is released and announced](https://upstash.com/blog/qstash-announcement)  (July 18, 2022)

<hr />

#### [Announcing Upstash CLI](https://upstash.com/blog/upstash-cli)  (May 16, 2022)

<hr />

#### [Introducing Redis 6 Compatibility](https://upstash.com/blog/redis-6)  (April 10, 2022)

<hr />

#### Strong Consistency Deprecated (March 29, 2022)

We have deprecated Strong Consistency mode for Redis databases due to its
performance impact. This will not be available for new databases. We are
planning to disable it on existing databases before the end of 2023. The
database owners will be notified via email.

<hr />

#### [Announcing Upstash Redis SDK v1.0.0](https://upstash.com/blog/upstash-redis-sdk-v1) (March 14, 2022)

<hr />

#### Support for Google Cloud (June 8, 2021)

Google Cloud is available for Upstash Redis databases. We initially support
US-Central-1 (Iowa) region. Check the
[get started guide](https://docs.upstash.com/redis/howto/getstartedgooglecloudfunctions).

<hr />

#### Support for AWS Japan (March 1, 2021)

こんにちは日本

Support for AWS Tokyo Region was the most requested feature by our users. Now
our users can create their database in AWS Asia Pacific (Tokyo) region
(ap-northeast-1). In addition to Japan, Upstash is available in the regions
us-west-1, us-east-1, eu-west-1.

Click [here](https://console.upstash.com) to start your database for free.

Click [here](https://roadmap.upstash.com) to request new regions to be
supported.

<hr />

#### Vercel Integration (February 22, 2021)

Upstash&Vercel integration has been released. Now you are able to integrate
Upstash to your project easily. We believe Upstash is the perfect database for
your applications thanks to its:

* Low latency data
* Per request pricing
* Durable storage
* Ease of use

Below are the resources about the integration:

See [how to guide](https://docs.upstash.com/redis/howto/vercelintegration).

See [integration page](https://vercel.com/integrations/upstash).

See
[Roadmap Voting app](https://github.com/upstash/roadmap)
as a showcase for the integration.

# Compliance
Source: https://upstash.com/docs/common/help/compliance

## Upstash Legal & Security Documents

* [Upstash Terms of Service](https://upstash.com/static/trust/terms.pdf)
* [Upstash Privacy Policy](https://upstash.com/static/trust/privacy.pdf)
* [Upstash Data Processing Agreement](https://upstash.com/static/trust/dpa.pdf)
* [Upstash Technical and Organizational Security Measures](https://upstash.com/static/trust/security-measures.pdf)
* [Upstash Subcontractors](https://upstash.com/static/trust/subprocessors.pdf)

## Is Upstash SOC2 Compliant?

Upstash Redis databases under Pro and Enterprise support plans are SOC2 compliant. Check our [trust page](https://trust.upstash.com/) for details.

## Is Upstash ISO-27001 Compliant?

We are in process of getting this certification. Contact us
([support@upstash.com](mailto:support@upstash.com)) to learn about the expected
date.

## Is Upstash GDPR Compliant?

Yes. For more information, see our
[Privacy Policy](https://upstash.com/static/trust/privacy.pdf). We acquire DPAs
from each [subcontractor](https://upstash.com/static/trust/subprocessors.pdf)
that we work with.

## Is Upstash HIPAA Compliant?

Yes. Upstash Redis is HIPAA compliant and we are in process of getting this compliance for our other products. See [Managing Healthcare Data](https://upstash.com/docs/redis/help/managing-healthcare-data) for more details.

## Is Upstash PCI Compliant?

Upstash does not store personal credit card information. We use Stripe for
payment processing. Stripe is a certified PCI Service Provider Level 1, which is
the highest level of certification in the payments industry.

## Does Upstash conduct vulnerability scanning and penetration tests?

Yes, we use third party tools and work with pen testers. We share the results
with Enterprise customers. Contact us
([support@upstash.com](mailto:support@upstash.com)) for more information.

## Does Upstash take backups?

Yes, we take regular snapshots of the data cluster to the AWS S3 platform.

## Does Upstash encrypt data?

Customers can enable TLS when creating a database or cluster, and we recommend this for production environments. Additionally, we encrypt data at rest upon customer request.

# Legal
Source: https://upstash.com/docs/common/help/legal

## Upstash Legal Documents

* [Upstash Terms of Service](https://upstash.com/trust/terms.pdf)
* [Upstash Privacy Policy](https://upstash.com/trust/privacy.pdf)
* [Upstash Subcontractors](https://upstash.com/trust/subprocessors.pdf)
* [Context7 Addendum](https://upstash.com/trust/context7addendum.pdf)
* [Data Processing Addendum](https://upstash.com/static/trust/dpa.pdf)

# Production Checklist
Source: https://upstash.com/docs/common/help/production-checklist

This checklist provides essential recommendations for securing and optimizing your Upstash databases for production workloads.

## Security Features

### Enable Prod Pack
Prod Pack provides enterprise-grade security and monitoring features:

* 99.99% uptime SLA
* SOC-2 Type 2 report available
* Encryption at Rest
* Advanced monitoring (Prometheus, Datadog)
* Multi-Zone High Availability
* High availability for read regions

<Note>
  Prod Pack is available as a $200/month add-on per database for all paid plans except Free tier.
</Note>

### Enable Credential Protection
Protect your database credentials (Prod Pack feature):

* Credentials are never stored in Upstash infrastructure
* Credentials are displayed only once during enablement
* Console features requiring database access are disabled

<Warning>
  Disabling this feature will permanently revoke current credentials and generate new ones.
</Warning>

### Configure IP Allowlist
Restrict database access to specific IP addresses:

* Available on all plans except Free tier
* Supports IPv4 addresses and CIDR blocks
* Multiple IP ranges can be configured

### Implement Redis ACL
Use Redis Access Control Lists to restrict user access:

* Available on all paid plans
* Create users with minimal required permissions
* Available for both TCP connections and REST API
* Use `ACL RESTTOKEN` command to generate REST tokens

### Enable Multi-Factor Authentication
Enable MFA on your Upstash account for enhanced security:

* Use your existing authentication provider (Google, GitHub, Amazon)
* Consider using a dedicated email/password account for production
* Force MFA for all team members to ensure consistent security
* Regularly review account access and team member permissions

### Secure Credential Management
Follow these best practices:

* Never hardcode credentials in your application code
* Use environment variables or secret management systems
* Reset passwords immediately if credentials are compromised
* Use Read-Only tokens for public-facing applications

## Network Security

### TLS Encryption
TLS is always enabled on Upstash Redis databases.

### VPC Peering (Enterprise)
Connect databases to your VPCs using private IP:

* Database becomes inaccessible from public networks
* Minimizes data transfer costs
* Available for Enterprise customers

## Monitoring & Observability

### Enable Advanced Monitoring
Prod Pack includes comprehensive monitoring:

* Prometheus integration
* Datadog integration
* Extended console metrics (up to one month)

## High Availability & Backup

### Enable Daily Backups
Configure automated daily backups for data protection:

* Available on all paid plans
* Backup retention up to 3 days with Prod Pack
* Hourly backups with customizable retention (Enterprise)

### Global Replication
For global applications, consider using Global Database:

* Distribute data across multiple regions
* Minimize latency for users worldwide
* Enhanced disaster recovery capabilities

## Compliance & Governance

### SOC-2 Compliance
Prod Pack and Enterprise plans include SOC-2 Type 2 compliance:

* Request SOC-2 report from [trust.upstash.com](https://trust.upstash.com/)
* Available for production workloads

### Enterprise Features
For enterprise customers:

* HIPAA compliance available
* SAML SSO integration
* Access logs available
* Custom resource allocation

## Pre-Production Checklist

Before going live, ensure you have:

* [ ] Prod Pack enabled (recommended)
* [ ] Credential Protection enabled
* [ ] IP Allowlist configured
* [ ] MFA enabled on your account
* [ ] Daily backups enabled
* [ ] Monitoring and alerts configured
* [ ] Environment variables secured
* [ ] Error handling tested

## Additional Resources

* [Security Features](/docs/redis/features/security)
* [Prod Pack & Enterprise](/docs/redis/overall/enterprise)
* [Backup & Restore](/docs/redis/features/backup)
* [Global Database](/docs/redis/features/globaldatabase)
* [Monitoring & Metrics](/docs/redis/howto/metrics-and-charts)
* [Compliance Information](/docs/common/help/compliance)
* [Professional Support](/docs/common/help/prosupport)

For additional assistance with production deployment, contact our support team at [support@upstash.com](mailto:support@upstash.com).

# Professional Support
Source: https://upstash.com/docs/common/help/prosupport

For all Upstash products, we manage everything for you and let you focus on more important things. If you ever need further help, our dedicated Professional Support team are here to ensure you get the most out of our platform, whether you’re just starting or scaling to new heights.

Professional Support is strongly recommended especially for customers who use Upstash as part of their production systems.

# Expert Guidance
Get direct access to our team of specialists who can provide insights, troubleshooting, and best practices tailored to your unique use case. In any urgent incident you might have, our Support team will be standing by and ready to join you for troubleshooting.

Professional Support package includes:

* **Guaranteed Response Time:** Rapid Response Time SLA to urgent support requests, ensuring your concerns are addressed promptly with a **24/7 coverage**.
* **Customer Onboarding:** A personalized session to guide you through utilizing our support services and reviewing your specific use case for a seamless start.
* **Quarterly Use Case Review & Health Check:** On-request sessions every quarter to review your use case and ensure optimal performance.
* **Dedicated Slack Channel:** Direct access to our team via a private Slack channel, so you can reach out whenever you need assistance.
* **Incident Support:** Video call support during critical incidents to provide immediate help and resolution.
* **Root Cause Analysis:** Comprehensive investigation and post-mortem analysis of critical incidents to identify and address the root cause.

# Response Time SLA
We understand that timely assistance is critical for production workloads, so your access to our Support team comes with 24/7 coverage and below SLA:

| Severity | Response Time |
| -------- | --------------|
| P1 - Production system down | 30 minutes |
| P2 - Production system impaired | 2 hours |
| P3 - Minor issue | 12 hours |
| P4 - General guidance | 24 hours |

## How to Reach Out?
As a Professional Support Customer, below are the **two methods** to reach out to Upstash Support Team, in case you need to utilize our services:

#### Starting a Chat
You will see a chatbox on the bottom right when viewing Upstash console, docs and website. Once you initiate a chat, Professional Support customers will be prompted to select a severity level:

  <img width="400" />

<Note>
To be able to see these options in chat, remember to sign into your Upstash Account first.
</Note>

If you select "P1 - Production down, no workaround", or "P2 - Production impaired with workaround" options, you will be triggering an alert for our team to urgently step in.

#### Sending an Email
Sending an email with details to support@upstash.com is another way to submit a support request. In case of an urgency, sending an email with details by using "urgent" keyword in email subject is another alternative to alert our team about a possible incident.

# Pricing
For pricing and further details about Professional Support, please contact us at support@upstash.com

# Uptime SLA
Source: https://upstash.com/docs/common/help/sla

This Service Level Agreement ("SLA") applies to Upstash resources with the Prod Pack add-on or Enterprise plans. It is clarified that this SLA is subject to the [terms of the Agreement](https://upstash.com/trust/terms.pdf), and does not derogate therefrom (capitalized terms, unless otherwise indicated herein, have the meaning specified in the Agreement).

To receive uptime SLA guarantees, you need to enable the Prod Pack add-on or be on an Enterprise plan for your resource. Learn more about [Prod Pack and Enterprise features for Redis](/docs/redis/overall/enterprise) or [QStash](/docs/qstash/overall/enterprise).

Upstash reserves the right to change the terms of this SLA by publishing updated
terms on its website, such change to be effective as of the date of publication.

### Uptime Guarantee

Upstash will use commercially reasonable efforts to make resources with Prod Pack add-on or Enterprise plans available with a Monthly Uptime Percentage of at least **99.99%**.

In the event any of the services do not meet the SLA, you will be eligible to
receive a Service Credit as described below.

| Monthly Uptime Percentage                           | Service Credit Percentage |
| --------------------------------------------------- | ------------------------- |
| Less than 99.99% but equal to or greater than 99.0% | 10%                       |
| Less than 99.0% but equal to or greater than 95.0%  | 30%                       |
| Less than 95.0%                                     | 60%                       |

### SLA Credits

Service Credits are calculated as a percentage of the monthly bill (excluding
one-time payments such as upfront payments) for the resource in the affected
region that did not meet the SLA.

Uptime percentages are recorded and published in the
[Upstash Status Page](https://status.upstash.com).

To receive a Service Credit, you should submit a claim by sending an email to
[support@upstash.com](mailto:support@upstash.com). Your credit request should be
received by us before the end of the second billing cycle after the incident
occurred.

We will apply any service credits against future payments for the applicable
services. At our discretion, we may issue the Service Credit to the credit card
you used. Service Credits will not entitle you to any refund or other payment. A
Service Credit will be applicable and issued only if the credit amount for the
applicable monthly billing cycle is greater than one dollar ($1 USD). Service
Credits may not be transferred or applied to any other account.

### Getting Uptime SLA Coverage

To receive uptime SLA guarantees for your resources, you need to upgrade to either:

* **Prod Pack**: An add-on per resource available to both pay-as-you-go and fixed-price plans
* **Enterprise Plan**: A custom plan that can cover one or more of your resources

You can activate Prod Pack on the resource details page in the console. For Enterprise plans, contact [support@upstash.com](mailto:support@upstash.com).

Learn more about [Prod Pack and Enterprise features for Redis](/docs/redis/overall/enterprise) or [QStash](/docs/qstash/overall/enterprise).

# Support & Contact Us
Source: https://upstash.com/docs/common/help/support

## Community

[Upstash Discord Channel](https://upstash.com/discord) is the best way to
interact with the community.

## Team

Regardless of your subscription plan, you can contact the team
via [support@upstash.com](mailto:support@upstash.com) for technical support as
well as questions and feedback.

## Follow Us

Follow us on [X](https://x.com/upstash).

## Enterprise Support

Get [Enterprise Support](/docs/common/help/prosupport) for your organization from the Upstash team.

# Uptime Monitor
Source: https://upstash.com/docs/common/help/uptime

## Status Page

You can track the uptime status of Upstash databases in
[Upstash Status Page](https://status.upstash.com)

## Latency Monitor

You can see the average latencies for different regions in
[Upstash Latency Monitoring](https://latency.upstash.com) page

# List Audit Logs
Source: https://upstash.com/docs/devops/developer-api/account/list_audit_logs

/devops/developer-api/openapi.yml get /auditlogs
This endpoint lists all audit logs of user.

# Authentication
Source: https://upstash.com/docs/devops/developer-api/authentication

The Upstash API requires API keys to authenticate requests. You can view and
manage API keys at the Upstash Console.

Upstash API uses HTTP Basic authentication. You should pass `EMAIL` and
`API_KEY` as basic authentication username and password respectively.

With a client such as `curl`, you can pass your credentials with the `-u`
option, as the following example shows:

```curl
curl https://api.upstash.com/v2/redis/databases -u EMAIL:API_KEY
```

Replace `EMAIL` and `API_KEY` with your email and API key.

# HTTP Status Codes
Source: https://upstash.com/docs/devops/developer-api/http_status_codes

| Code | Description               |                                                                                 |
| ---- | ------------------------- | ------------------------------------------------------------------------------- |
| 200  | **OK**                    | Indicates that a request completed successfully and the response contains data. |
| 400  | **Bad Request**           | Your request is invalid.                                                        |
| 401  | **Unauthorized**          | Your API key is wrong.                                                          |
| 403  | **Forbidden**             | The kitten requested is hidden for administrators only.                         |
| 404  | **Not Found**             | The specified kitten could not be found.                                        |
| 405  | **Method Not Allowed**    | You tried to access a kitten with an invalid method.                            |
| 406  | **Not Acceptable**        | You requested a format that isn't JSON.                                         |
| 429  | **Too Many Requests**     | You're requesting too many kittens! Slow down!                                  |
| 500  | **Internal Server Error** | We had a problem with our server. Try again later.                              |
| 503  | **Service Unavailable**   | We're temporarily offline for maintenance. Please try again later.              |

# Getting Started
Source: https://upstash.com/docs/devops/developer-api/introduction

Using Upstash API, you can develop applications that can create and manage
Upstash products and resources. You can automate everything that
you can do in the console. To use developer API, you need to create an API key
in the console.

<Note>

The Developer API is only available to native Upstash accounts. Accounts created via third-party platforms like Vercel or Fly.io are not supported.

</Note>

### Create an API key

1. Log in to the console then in the left menu click the
   `Account > Management API` link.

2. Click the `Create API Key` button.

  <img />

3. Enter a name for your key. You can not use the same name for multiple keys.

  <img />

  <img />

You need to download or copy/save your API key. Upstash does not remember or
keep your API for security reasons. So if you forget your API key, it becomes
useless; you need to create a new one.

<br />

You can create multiple keys. It is recommended to use different keys in
different applications. By default one user can create up to 37 API keys. If you
need more than that, please send us an email at
[support@upstash.com](mailto:support@upstash.com)

### Deleting an API key

When an API key is exposed (e.g. accidentally shared in a public repository) or
not being used anymore; you should delete it. You can delete the API keys in
`Account > API Keys` screen.

### Roadmap

**Role based access:** You will be able to create API keys with specific
privileges. For example you will be able to create a key with read-only access.

**Stats:** We will provide reports based on usage of your API keys.

# Create Backup
Source: https://upstash.com/docs/devops/developer-api/redis/backup/create_backup

/devops/developer-api/openapi.yml post /redis/create-backup/{id}
This endpoint creates a backup for a Redis database.

# Delete Backup
Source: https://upstash.com/docs/devops/developer-api/redis/backup/delete_backup

/devops/developer-api/openapi.yml delete /redis/delete-backup/{id}/{backup_id}
This endpoint deletes a backup of a Redis database.

# Disable Daily Backup
Source: https://upstash.com/docs/devops/developer-api/redis/backup/disable_dailybackup

/devops/developer-api/openapi.yml patch /redis/disable-dailybackup/{id}
This endpoint disables daily backup for a Redis database.

# Enable Daily Backup
Source: https://upstash.com/docs/devops/developer-api/redis/backup/enable_dailybackup

/devops/developer-api/openapi.yml patch /redis/enable-dailybackup/{id}
This endpoint enables daily backup for a Redis database.

# List Backup
Source: https://upstash.com/docs/devops/developer-api/redis/backup/list_backup

/devops/developer-api/openapi.yml get /redis/list-backup/{id}
This endpoint lists all backups for a Redis database.

# Restore Backup
Source: https://upstash.com/docs/devops/developer-api/redis/backup/restore_backup

/devops/developer-api/openapi.yml post /redis/restore-backup/{id}
This endpoint restores data from an existing backup.

# Change Database Plan
Source: https://upstash.com/docs/devops/developer-api/redis/change_plan

/devops/developer-api/openapi.yml post /redis/{id}/change-plan
This endpoint changes the plan of a Redis database.

# Create Redis Database
Source: https://upstash.com/docs/devops/developer-api/redis/create_database_global

/devops/developer-api/openapi.yml post /redis/database
This endpoint creates a new Redis database.

To create a database manually, you can use the [Upstash Console](https://console.upstash.com/redis).

For a quick development database, POST to https://upstash.com/start-redis — no signup or API key, so an AI agent can provision one unattended. Expires in 72 hours unless claimed. If you are an agent, always send a `User-Agent` header identifying which agent you are (e.g. `claude-code`, `cursor`, `codex`, `opencode`) on every request you make to this API.

# Delete Database
Source: https://upstash.com/docs/devops/developer-api/redis/delete_database

/devops/developer-api/openapi.yml delete /redis/database/{id}
This endpoint deletes a database.

# Disable Auto Upgrade
Source: https://upstash.com/docs/devops/developer-api/redis/disable_autoscaling

/devops/developer-api/openapi.yml post /redis/disable-autoupgrade/{id}
This endpoint disables Auto Upgrade for given database.

# Disable Eviction
Source: https://upstash.com/docs/devops/developer-api/redis/disable_eviction

/devops/developer-api/openapi.yml post /redis/disable-eviction/{id}
This endpoint disables eviction for given database.

# Enable Auto Upgrade
Source: https://upstash.com/docs/devops/developer-api/redis/enable_autoscaling

/devops/developer-api/openapi.yml post /redis/enable-autoupgrade/{id}
This endpoint enables Auto Upgrade for given database.

# Enable Eviction
Source: https://upstash.com/docs/devops/developer-api/redis/enable_eviction

/devops/developer-api/openapi.yml post /redis/enable-eviction/{id}
This endpoint enables eviction for given database.

# Enable TLS
Source: https://upstash.com/docs/devops/developer-api/redis/enable_tls

/devops/developer-api/openapi.yml post /redis/enable-tls/{id}
This endpoint enables tls on a database.

# Get Database
Source: https://upstash.com/docs/devops/developer-api/redis/get_database

/devops/developer-api/openapi.yml get /redis/database/{id}
This endpoint gets details of a database.

# Get Database Stats
Source: https://upstash.com/docs/devops/developer-api/redis/get_database_stats

/devops/developer-api/openapi.yml get /redis/stats/{id}
This endpoint gets detailed stats of a database.

# List Databases
Source: https://upstash.com/docs/devops/developer-api/redis/list_databases

/devops/developer-api/openapi.yml get /redis/databases
This endpoint list all databases of user.

# Move To Team
Source: https://upstash.com/docs/devops/developer-api/redis/moveto_team

/devops/developer-api/openapi.yml post /redis/move-to-team
This endpoint moves database under a target team

# Rename Database
Source: https://upstash.com/docs/devops/developer-api/redis/rename_database

/devops/developer-api/openapi.yml post /redis/rename/{id}
This endpoint renames a database.

# Reset Password
Source: https://upstash.com/docs/devops/developer-api/redis/reset_password

/devops/developer-api/openapi.yml post /redis/reset-password/{id}
This endpoint updates the password of a database.

# Update Database Budget
Source: https://upstash.com/docs/devops/developer-api/redis/update_budget

/devops/developer-api/openapi.yml patch /redis/update-budget/{id}
This endpoint updates the monthly budget of a Redis database.

# Update Regions
Source: https://upstash.com/docs/devops/developer-api/redis/update_regions

/devops/developer-api/openapi.yml post /redis/update-regions/{id}
Update the regions of a database

# Add Team Member
Source: https://upstash.com/docs/devops/developer-api/teams/add_team_member

/devops/developer-api/openapi.yml post /teams/member
This endpoint adds a new team member to the specified team.

# Create Team
Source: https://upstash.com/docs/devops/developer-api/teams/create_team

/devops/developer-api/openapi.yml post /team
This endpoint creates a new team.

# Delete Team
Source: https://upstash.com/docs/devops/developer-api/teams/delete_team

/devops/developer-api/openapi.yml delete /team/{id}
This endpoint deletes a team.

# Delete Team Member
Source: https://upstash.com/docs/devops/developer-api/teams/delete_team_member

/devops/developer-api/openapi.yml delete /teams/member
This endpoint deletes a team member from the specified team.

# Get Team Members
Source: https://upstash.com/docs/devops/developer-api/teams/get_team_members

/devops/developer-api/openapi.yml get /teams/{team_id}
This endpoint list all members of a team.

# List Teams
Source: https://upstash.com/docs/devops/developer-api/teams/list_teams

/devops/developer-api/openapi.yml get /teams
This endpoint lists all teams of user.

# Create Index
Source: https://upstash.com/docs/devops/developer-api/vector/create_index

/devops/developer-api/openapi.yml post /vector/index
This endpoint creates an index.

# Delete Index
Source: https://upstash.com/docs/devops/developer-api/vector/delete_index

/devops/developer-api/openapi.yml delete /vector/index/{id}
This endpoint deletes an index.

# Get Index
Source: https://upstash.com/docs/devops/developer-api/vector/get_index

/devops/developer-api/openapi.yml get /vector/index/{id}
This endpoint returns the data associated to a index.

# List Indices
Source: https://upstash.com/docs/devops/developer-api/vector/list_indices

/devops/developer-api/openapi.yml get /vector/index
This endpoint returns the data related to all indices of an account as a list.

# Rename Index
Source: https://upstash.com/docs/devops/developer-api/vector/rename_index

/devops/developer-api/openapi.yml post /vector/index/{id}/rename
This endpoint is used to change the name of an index.

# Reset Index Passwords
Source: https://upstash.com/docs/devops/developer-api/vector/reset_index_passwords

/devops/developer-api/openapi.yml post /vector/index/{id}/reset-password
This endpoint is used to reset regular and readonly tokens of an index.

# Set Index Plan
Source: https://upstash.com/docs/devops/developer-api/vector/set_index_plan

/devops/developer-api/openapi.yml post /vector/index/{id}/setplan
This endpoint is used to change the plan of an index.

# Transfer Index
Source: https://upstash.com/docs/devops/developer-api/vector/transfer_index

/devops/developer-api/openapi.yml post /vector/index/{id}/transfer
This endpoint is used to transfer an index to another team. 
Transferring to a personal account is not supported. However, transferring an index from a personal account to a team is allowed.

# Overview
Source: https://upstash.com/docs/devops/pulumi/overview

The Upstash Pulumi Provider lets you manage [Upstash](https://upstash.com) Redis resources programmatically.

You can find the Github Repository [here](https://github.com/upstash/pulumi-upstash).

## Installing

This package is available for several languages/platforms:

### Node.js (JavaScript/TypeScript)

To use from JavaScript or TypeScript in Node.js, install using either `npm`:

```bash
npm install @upstash/pulumi
```

or `yarn`:

```bash
yarn add @upstash/pulumi
```

### Python

To use from Python, install using `pip`:

```bash
pip install upstash_pulumi
```

### Go

To use from Go, use `go get` to grab the latest version of the library:

```bash
go get github.com/upstash/pulumi-upstash/sdk/go/...
```

## Configuration

The following configuration points are available for the `upstash` provider:

* `upstash:apiKey` (environment: `UPSTASH_API_KEY`) - the API key for `upstash`. Can be obtained from the [console](https://console.upstash.com).
* `upstash:email` (environment: `UPSTASH_EMAIL`) - owner email of the resources

## Some Examples

### TypeScript:

```typescript
import * as pulumi from "@pulumi/pulumi";
import * as upstash from "@upstash/pulumi";

// multiple redis databases in a single for loop

for (let i = 0; i < 5; i++) {
  new upstash.RedisDatabase("mydb" + i, {
    databaseName: "pulumi-ts-db" + i,
    region: "eu-west-1",
    tls: true,
  });
}
```

### Go:

```go
package main

import (
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
	"github.com/upstash/pulumi-upstash/sdk/go/upstash"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {

		createdTeam, err := upstash.NewTeam(ctx, "exampleTeam", &upstash.TeamArgs{
			TeamName: pulumi.String("pulumi go team"),
			CopyCc:   pulumi.Bool(false),
			TeamMembers: pulumi.StringMap{
				"<owner-email>": pulumi.String("owner"),
				"<some-other-user-email>":   pulumi.String("dev"),
			},

		})
		if err != nil {
			return err
		}
		return nil
	})
}

```

# Terraform
Source: https://upstash.com/docs/devops/terraform

# upstash_qstash_endpoint_data
Source: https://upstash.com/docs/devops/terraform/data_sources/upstash_qstash_endpoint_data

<RequestExample>

```hcl example.tf
data "upstash_qstash_endpoint_data" "exampleQStashEndpointData" {
  endpoint_id = resource.upstash_qstash_endpoint.exampleQStashEndpoint.endpoint_id
}
```

</RequestExample>

## Schema

### Required

<ParamField query="topic_id" type="string" required>
  Topic Id that the endpoint is added to
</ParamField>

### Read-Only

<ResponseField name="endpoint_id" type="string">
  Unique QStash Endpoint ID
</ResponseField>

<ResponseField name="id" type="number">
  The ID of this resource.
</ResponseField>

<ResponseField name="url" type="string">
  Unique QStash Topic Name for Endpoint
</ResponseField>

# upstash_qstash_schedule_data
Source: https://upstash.com/docs/devops/terraform/data_sources/upstash_qstash_schedule_data

<RequestExample>

```hcl example.tf
data "upstash_qstash_schedule_data" "exampleQStashScheduleData" {
  schedule_id = resource.upstash_qstash_schedule.exampleQStashSchedule.schedule_id
}
```

</RequestExample>

## Schema

### Required

<ParamField query="schedule_id" type="string" required>
  Unique QStash Schedule ID for requested schedule
</ParamField>

### Read-Only

<ResponseField name="body" type="string">
  Body to send for the POST request in string format. Needs escaping () double
  quotes.
</ResponseField>
<ResponseField name="created_at" type="number">
  Creation time for QStash Schedule
</ResponseField>

<ResponseField name="cron" type="string">
  Cron string for QStash Schedule
</ResponseField>

<ResponseField name="destination" type="string">
  Destination for QStash Schedule. Either Topic ID or valid URL
</ResponseField>

<ResponseField name="forward_headers" type="map(string)">
  Forward headers to your API
</ResponseField>

<ResponseField name="id" type="string">
  The ID of this resource.
</ResponseField>

<ResponseField name="not_before" type="number">
  Start time for QStash Scheduling.
</ResponseField>

<ResponseField name="retries" type="number">
  Retries for QStash Schedule requests.
</ResponseField>

# upstash_qstash_topic_data
Source: https://upstash.com/docs/devops/terraform/data_sources/upstash_qstash_topic_data

<RequestExample>

```hcl example.tf
data "upstash_qstash_topic_data" "exampleQstashTopicData" {
  topic_id = resource.upstash_qstash_topic.exampleQstashTopic.topic_id
}
```

</RequestExample>

## Schema

### Required

<ParamField query="topic_id" type="string" required>
  Unique QStash Topic ID for requested topic
</ParamField>

### Read-Only

<ResponseField name="endpoints" type="list(map(string))">
  Endpoints for the QStash Topic
</ResponseField>

<ResponseField name="id" type="string">
  The ID of this resource.
</ResponseField>

<ResponseField name="name" type="string">
  Name of the QStash Topic
</ResponseField>

# upstash_redis_database_data
Source: https://upstash.com/docs/devops/terraform/data_sources/upstash_redis_database_data

<RequestExample>

```hcl example.tf
data "upstash_redis_database_data" "exampleDBData" {
  database_id = resource.upstash_redis_database.exampleDB.database_id
}
```

</RequestExample>

## Schema

### Required

<ParamField query="database_id" type="string" required>
  Unique Database ID for created database
</ParamField>

### Read-Only

<ResponseField name="auto_scale" type="bool">
  Upgrade to higher plans automatically when it hits quotas
</ResponseField>

<ResponseField name="creation_time" type="number">
  Creation time of the database
</ResponseField>

<ResponseField name="database_name" type="string">
  Name of the database
</ResponseField>

<ResponseField name="database_type" type="string">
  Type of the database
</ResponseField>

<ResponseField name="db_daily_bandwidth_limit" type="number">
  Daily bandwidth limit for the database
</ResponseField>

<ResponseField name="db_disk_threshold" type="number">
  Disk threshold for the database
</ResponseField>

<ResponseField name="db_max_clients" type="number">
  Max clients for the database
</ResponseField>

<ResponseField name="db_max_commands_per_second" type="number">
  Max commands per second for the database
</ResponseField>

<ResponseField name="db_max_entry_size" type="number">
  Max entry size for the database
</ResponseField>

<ResponseField name="db_max_request_size" type="number">
  Max request size for the database
</ResponseField>

<ResponseField name="db_memory_threshold" type="number">
  Memory threshold for the database
</ResponseField>

<ResponseField name="endpoint" type="string">
  Database URL for connection
</ResponseField>

<ResponseField name="id" type="string">
  The ID of this resource.
</ResponseField>

<ResponseField name="password" type="string">
  Password of the database
</ResponseField>

<ResponseField name="port" type="number">
  Port of the endpoint
</ResponseField>

<ResponseField name="primary_region" type="string">
  Primary region for the database (Only works if region='global'. Can be one of
  [us-east-1, us-west-1, us-west-2, eu-central-1, eu-west-1, sa-east-1,
  ap-southeast-1, ap-southeast-2])
</ResponseField>

<ResponseField name="read_only_rest_token" type="string">
  Rest Token for the database.
</ResponseField>

<ResponseField name="read_regions" type="set(string)">
  Read regions for the database (Only works if region='global' and
  primary_region is set. Can be any combination of [us-east-1, us-west-1,
  us-west-2, eu-central-1, eu-west-1, sa-east-1, ap-southeast-1,
  ap-southeast-2], excluding the one given as primary.)
</ResponseField>

<ResponseField name="region" type="string">
  Region of the database. Possible values are: `global`, `eu-west-1`,
  `us-east-1`, `us-west-1`, `ap-northeast-1` , `eu-central1`
</ResponseField>

<ResponseField name="rest_token" type="string">
  Rest Token for the database.
</ResponseField>

<ResponseField name="state" type="string">
  State of the database
</ResponseField>

<ResponseField name="tls" type="bool">
  When enabled, data is encrypted in transit. (If changed to false from true,
  results in deletion and recreation of the resource)
</ResponseField>

<ResponseField name="user_email" type="string">
  User email for the database
</ResponseField>

# upstash_team_data
Source: https://upstash.com/docs/devops/terraform/data_sources/upstash_team_data

<RequestExample>

```hcl example.tf
data "upstash_team_data" "teamData" {
  team_id = resource.upstash_team.exampleTeam.team_id
}
```

</RequestExample>

## Schema

### Required

<ParamField query="team_id" type="string" required>
  Unique Cluster ID for created cluster
</ParamField>

### Read-Only

<ResponseField name="copy_cc" type="bool">
  Whether Credit Card is copied
</ResponseField>

<ResponseField name="id" type="number">
  The ID of this resource.
</ResponseField>

<ResponseField name="team_members" type="map(string)">
  Members of the team. (Owner must be specified, which is the owner of the api
  key.)
</ResponseField>

<ResponseField name="team_name" type="string">
  Name of the team
</ResponseField>

# Overview
Source: https://upstash.com/docs/devops/terraform/overview

The Upstash Terraform Provider lets you manage Upstash Redis resources programmatically.

You can find the Github Repository for the Terraform Provider [here](https://github.com/upstash/terraform-provider-upstash).

## Installation

```hcl
terraform {
  required_providers {
    upstash = {
      source = "upstash/upstash"
      version = "x.x.x"
    }
  }
}

provider "upstash" {
  email    = var.email
  api_key  = var.api_key
}
```

`email` is your registered email in Upstash.

`api_key` can be generated from Upstash Console. For more information please check our [docs](https://docs.upstash.com/howto/developerapi).

## Create Database Using Terraform

Here example code snippet that creates database:

```hcl
resource "upstash_redis_database" "redis" {
  database_name = "db-name"
  region        = "eu-west-1"
  tls           = "true"
  multi_zone    = "false"
}
```

## Import Resources From Outside of Terraform

To import resources created outside of the terraform provider, simply create the resource in .tf file as follows:

```hcl
resource "upstash_redis_database" "redis" {}
```

after this, you can run the command:

```
terraform import upstash_redis_database.redis <db-id>
```

Above example is given for an Upstash Redis database. You can import all of the resources by changing the resource type and providing the resource id.

You can check full spec and [doc from here](https://registry.terraform.io/providers/upstash/upstash/latest/docs).

## Support, Bugs Reports, Feature Requests

If you need support then you can ask your questions Upstash Team in [upstash.com](https://upstash.com) chat widget.

There is also discord channel available for community. [Please check here](https://docs.upstash.com/help/support) for more information.

# upstash_qstash_endpoint
Source: https://upstash.com/docs/devops/terraform/resources/upstash_qstash_endpoint

<RequestExample>

```hcl example.tf
resource "upstash_qstash_endpoint" "exampleQStashEndpoint" {
  url      = "https://***.***"
  topic_id = resource.upstash_qstash_topic.exampleQstashTopic.topic_id
}
```

</RequestExample>

## Schema

### Required

<ParamField query="topic_id" type="string" required>
  Topic ID that the endpoint is added to
</ParamField>

<ParamField query="url" type="string" required>
  URL of the endpoint
</ParamField>

### Read-Only

<ResponseField name="endpoint_id" type="string">
  Unique QStash endpoint ID
</ResponseField>

<ResponseField name="id" type="string">
  The ID of this resource.
</ResponseField>

<ResponseField name="topic_name" type="string">
  Unique QStash topic name for endpoint
</ResponseField>

# upstash_qstash_schedule
Source: https://upstash.com/docs/devops/terraform/resources/upstash_qstash_schedule

<RequestExample>

```hcl example.tf
resource "upstash_qstash_schedule" "exampleQStashSchedule" {
  destination = resource.upstash_qstash_topic.exampleQstashTopic.topic_id
  cron        = "* * * * */2"

  # or simply provide a link
  # destination = "https://***.***"
}
```

</RequestExample>

## Schema

### Required

<ParamField query="cron" type="string" required>
  Cron string for QStash Schedule
</ParamField>

<ParamField query="destination" type="string" required>
  Destination for QStash Schedule. Either Topic ID or valid URL
</ParamField>

### Optional

<ParamField query="body" type="string">
  Body to send for the POST request in string format. Needs escaping () double
  quotes.
</ParamField>

<ParamField query="callback" type="string">
  Callback URL for QStash Schedule.
</ParamField>

<ParamField query="content_based_deduplication" type="bool">
  Content based deduplication for QStash Scheduling.
</ParamField>

<ParamField query="content_type" type="string">
  Content type for QStash Scheduling.
</ParamField>

<ParamField query="deduplicatoin_id" type="string">
  Deduplication ID for QStash Scheduling.
</ParamField>

<ParamField query="delay" type="string">
  Delay for QStash Schedule.
</ParamField>

<ParamField query="forward_headers" type="map(string)">
  Forward headers to your API
</ParamField>

<ParamField query="not_before" type="number">
  Start time for QStash Scheduling.
</ParamField>

<ParamField query="retries" type="number">
  Retries for QStash Schedule requests.
</ParamField>

### Read-Only

<ResponseField name="created_at" type="number">
  Creation time for QStash Schedule.
</ResponseField>

<ResponseField name="id" type="string">
  The ID of this resource.
</ResponseField>

<ResponseField name="schedule_id" type="string">
  Unique QStash Schedule ID for requested schedule
</ResponseField>

# upstash_qstash_topic
Source: https://upstash.com/docs/devops/terraform/resources/upstash_qstash_topic

<RequestExample>

```hcl example.tf
resource "upstash_qstash_topic" "exampleQStashTopic" {
  name = "exampleQStashTopicName"
}
```

</RequestExample>

## Schema

### Required

<ParamField query="name" type="string" required>
  Name of the QStash topic
</ParamField>

### Read-Only

<ResponseField name="endpoints" type="list(map(string))">
  Endpoints for the QStash topic
</ResponseField>

<ResponseField name="id" type="string">
  The ID of this resource.
</ResponseField>

<ResponseField name="topic_id" type="string">
  Unique QStash topic ID for requested topic
</ResponseField>

# upstash_redis_database
Source: https://upstash.com/docs/devops/terraform/resources/upstash_redis_database

<RequestExample>

```hcl example.tf
resource "upstash_redis_database" "exampleDB" {
  database_name = "Terraform DB6"
  region        = "eu-west-1"
  tls           = "true"
  multizone     = "true"
}
```

</RequestExample>

## Schema

### Required

<ParamField query="database_name" type="string" required>
  Name of the database
</ParamField>

<ParamField query="region" type="string" required>
  Region of the database. Possible values are: `global`, `eu-west-1`,
  `us-east-1`, `us-west-1`, `ap-northeast-1` , `eu-central1`
</ParamField>

### Optional

<ParamField query="auto_scale" type="bool">
  Upgrade to higher plans automatically when it hits quotas
</ParamField>

<ParamField query="eviction" type="bool">
  Enable eviction, to evict keys when your database reaches the max size
</ParamField>

<ParamField query="primary_region" type="string">
  Primary region for the database (Only works if region='global'. Can be one of
  [us-east-1, us-west-1, us-west-2, eu-central-1, eu-west-1, sa-east-1,
  ap-southeast-1, ap-southeast-2])
</ParamField>

<ParamField query="read_regions" type="set(string)">
  Read regions for the database (Only works if region='global' and
  primary_region is set. Can be any combination of [us-east-1, us-west-1,
  us-west-2, eu-central-1, eu-west-1, sa-east-1, ap-southeast-1,
  ap-southeast-2], excluding the one given as primary.)
</ParamField>

<ParamField query="tls" type="bool">
  When enabled, data is encrypted in transit. (If changed to false from true,
  results in deletion and recreation of the resource)
</ParamField>

### Read-Only

<ResponseField name="creation_time" type="number">
  Creation time of the database
</ResponseField>

<ResponseField name="database_id" type="string">
  Unique Database ID for created database
</ResponseField>

<ResponseField name="database_type" type="string">
  Type of the database
</ResponseField>

<ResponseField name="db_daily_bandwidth_limit" type="number">
  Daily bandwidth limit for the database
</ResponseField>

<ResponseField name="db_disk_threshold" type="number">
  Disk threshold for the database
</ResponseField>

<ResponseField name="db_max_clients" type="number">
  Max clients for the database
</ResponseField>

<ResponseField name="db_max_commands_per_second" type="number">
  Max commands per second for the database
</ResponseField>

<ResponseField name="db_max_entry_size" type="number">
  Max entry size for the database
</ResponseField>

<ResponseField name="db_max_request_size" type="number">
  Max request size for the database
</ResponseField>

<ResponseField name="db_memory_threshold" type="number">
  Memory threshold for the database
</ResponseField>

<ResponseField name="endpoint" type="string">
  Database URL for connection
</ResponseField>

<ResponseField name="id" type="string">
  The ID of this resource.
</ResponseField>

<ResponseField name="password" type="string">
  Password of the database
</ResponseField>

<ResponseField name="port" type="number">
  Port of the endpoint
</ResponseField>

<ResponseField name="read_only_rest_token" type="string">
  Rest Token for the database.
</ResponseField>

<ResponseField name="rest_token" type="string">
  Rest Token for the database.
</ResponseField>

<ResponseField name="state" type="string">
  State of the database
</ResponseField>

<ResponseField name="user_email" type="string">
  User email for the database
</ResponseField>

# upstash_team
Source: https://upstash.com/docs/devops/terraform/resources/upstash_team

<RequestExample>

```hcl example.tf
resource "upstash_team" "exampleTeam" {
  team_name    = "TerraformTeam"
  copy_cc      = false
  team_members = {
      # Owner is the owner of the api_key.
      "X@Y.Z": "owner",
      "A@B.C": "dev",
      "E@E.F": "finance",
    }
}
```

</RequestExample>

## Schema

### Required

<ParamField query="copy_cc" type="bool" required>
  Whether Credit Card is copied
</ParamField>

<ParamField query="team_members" type="map(string)" required>
  Members of the team. (Owner must be specified, which is the owner of the api
  key.)
</ParamField>

<ParamField query="team_name" type="string" required>
  Name of the team
</ParamField>

### Read-Only

<ResponseField name="id" type="string">
  The ID of this resource.
</ResponseField>

<ResponseField name="team_id" type="string">
  Unique Cluster ID for created cluster
</ResponseField>

# Bg color codes
Source: https://upstash.com/docs/img/bg-color-codes

Recommended Background Color Transition:

Primary: #34D399 (Emerald Green)
Secondary: #00E9A3 (Cyan Green)

# Get Started
Source: https://upstash.com/docs/introduction

import { Hero, SectionHead, ProductGrid, AgentResources, Concepts, Community } from "/snippets/landing.jsx";

<Hero />

<div className="u-wrap">
  <div id="products" />
  <SectionHead eyebrow="Products" title="Pick a product and start building" sub={<>See <a href="https://upstash.com/pricing">pricing</a> for each product's plans, billing, and limits.</>} />
  <ProductGrid />

  <SectionHead eyebrow="Agent Resources" title="Build with AI agents" sub="Tools to manage and build on Upstash from coding agents and LLMs." />
  <AgentResources />

  <SectionHead eyebrow="Concepts" title="How Upstash works" />
  <Concepts />

  <SectionHead eyebrow="Community" title="Get in touch" />
  <Community />
</div>

# Bulk Delete DLQ messages
Source: https://upstash.com/docs/qstash/api-reference/dlq/bulk-delete-dlq-messages

/qstash/openapi.yaml delete /v2/dlq
Delete multiple messages from the DLQ

# Bulk Retry DLQ messages
Source: https://upstash.com/docs/qstash/api-reference/dlq/bulk-retry-dlq-messages

/qstash/openapi.yaml post /v2/dlq/retry
Retry delivery of multiple messages from the DLQ

# Delete a DLQ message
Source: https://upstash.com/docs/qstash/api-reference/dlq/delete-a-dlq-message

/qstash/openapi.yaml delete /v2/dlq/{dlqId}
Manually remove a message from the DLQ

# Get a DLQ message
Source: https://upstash.com/docs/qstash/api-reference/dlq/get-a-dlq-message

/qstash/openapi.yaml get /v2/dlq/{dlqId}
Get a specific message from the DLQ

# List DLQ messages
Source: https://upstash.com/docs/qstash/api-reference/dlq/list-dlq-messages

/qstash/openapi.yaml get /v2/dlq
List and paginate through all messages currently in the DLQ

# Retry a DLQ message
Source: https://upstash.com/docs/qstash/api-reference/dlq/retry-a-dlq-message

/qstash/openapi.yaml post /v2/dlq/retry/{dlqId}
Retry delivery of a message from the DLQ

# Get Flow Control Key
Source: https://upstash.com/docs/qstash/api-reference/flow-control/get-flow-control-key

/qstash/openapi.yaml get /v2/flowControl/{flowControlKey}
Get details of a specific Flow Control key

# Get Global Parallelism
Source: https://upstash.com/docs/qstash/api-reference/flow-control/get-global-parallelism

/qstash/openapi.yaml get /v2/globalParallelism
Returns the current global parallelism usage across all flow control keys

# List Flow Control Keys
Source: https://upstash.com/docs/qstash/api-reference/flow-control/list-flow-control-keys

/qstash/openapi.yaml get /v2/flowControl
List all Flow Control keys

# Pause Flow Control Key
Source: https://upstash.com/docs/qstash/api-reference/flow-control/pause-flow-control-key

/qstash/openapi.yaml post /v2/flowControl/{flowControlKey}/pause
Pauses the delivery of messages associated with a specific flow-control key.

# Pin Configuration for Flow Control Key
Source: https://upstash.com/docs/qstash/api-reference/flow-control/pin-configuration-for-flow-control-key

/qstash/openapi.yaml post /v2/flowControl/{flowControlKey}/pin
Pins a processing configuration for a specific flow-control key.

# Reset Rate for Flow Control Key
Source: https://upstash.com/docs/qstash/api-reference/flow-control/reset-rate-for-flow-control-key

/qstash/openapi.yaml post /v2/flowControl/{flowControlKey}/resetRate
Resets the rate configuration state for a specific flow-control key.

# Resume Flow Control Key
Source: https://upstash.com/docs/qstash/api-reference/flow-control/resume-flow-control-key

/qstash/openapi.yaml post /v2/flowControl/{flowControlKey}/resume
Resumes the delivery of messages associated with a specific flow-control key.

# Unpin Configuration for Flow Control Key
Source: https://upstash.com/docs/qstash/api-reference/flow-control/unpin-configuration-for-flow-control-key

/qstash/openapi.yaml post /v2/flowControl/{flowControlKey}/unpin
Removes the pinned configuration for a specific flow-control key.

# List Logs
Source: https://upstash.com/docs/qstash/api-reference/logs/list-logs

/qstash/openapi.yaml get /v2/logs
Paginate through logs of published messages

# Batch Messages
Source: https://upstash.com/docs/qstash/api-reference/messages/batch-messages

/qstash/openapi.yaml post /v2/batch
Send multiple messages in a single request

# Bulk Cancel Messages
Source: https://upstash.com/docs/qstash/api-reference/messages/bulk-cancel-messages

/qstash/openapi.yaml delete /v2/messages
Delete all pending messages

# Cancel a Message
Source: https://upstash.com/docs/qstash/api-reference/messages/cancel-a-message

/qstash/openapi.yaml delete /v2/messages/{messageId}
Cancel a pending message

# Enqueue a Message
Source: https://upstash.com/docs/qstash/api-reference/messages/enqueue-a-message

/qstash/openapi.yaml post /v2/enqueue/{queueName}/{destination}
Enqueue a message to the specified queue

# Get a Message
Source: https://upstash.com/docs/qstash/api-reference/messages/get-a-message

/qstash/openapi.yaml get /v2/messages/{messageId}
Retrieve details of a specific message

# Publish a Message
Source: https://upstash.com/docs/qstash/api-reference/messages/publish-a-message

/qstash/openapi.yaml post /v2/publish/{destination}
Publish a message to the specified destination

# Delete a queue
Source: https://upstash.com/docs/qstash/api-reference/queues/delete-a-queue

/qstash/openapi.yaml delete /v2/queues/{queueName}
Deletes a queue

# Get a Queue
Source: https://upstash.com/docs/qstash/api-reference/queues/get-a-queue

/qstash/openapi.yaml get /v2/queues/{queueName}
Get details of a specific queue

# List Queues
Source: https://upstash.com/docs/qstash/api-reference/queues/list-queues

/qstash/openapi.yaml get /v2/queues
List all your queues

# Pause Queue
Source: https://upstash.com/docs/qstash/api-reference/queues/pause-queue

/qstash/openapi.yaml post /v2/queues/{queueName}/pause
Pause a queue to stop the delivery of enqueued messages

# Resume Queue
Source: https://upstash.com/docs/qstash/api-reference/queues/resume-queue

/qstash/openapi.yaml post /v2/queues/{queueName}/resume
Resumes a queue to starts the delivery of enqueued messages

# Upsert a Queue
Source: https://upstash.com/docs/qstash/api-reference/queues/upsert-a-queue

/qstash/openapi.yaml post /v2/queues
Updates or creates a queue

# Create a Schedule
Source: https://upstash.com/docs/qstash/api-reference/schedules/create-a-schedule

/qstash/openapi.yaml post /v2/schedules/{destination}
Create a schedule to send messages periodically

# Delete a Schedule
Source: https://upstash.com/docs/qstash/api-reference/schedules/delete-a-schedule

/qstash/openapi.yaml delete /v2/schedules/{scheduleId}
Delete a schedule

# Get a Schedule
Source: https://upstash.com/docs/qstash/api-reference/schedules/get-a-schedule

/qstash/openapi.yaml get /v2/schedules/{scheduleId}
Get details of a specific schedule

# List schedules
Source: https://upstash.com/docs/qstash/api-reference/schedules/list-schedules

/qstash/openapi.yaml get /v2/schedules
List all schedules

# Pause a Schedule
Source: https://upstash.com/docs/qstash/api-reference/schedules/pause-a-schedule

/qstash/openapi.yaml post /v2/schedules/{scheduleId}/pause
Pause a Schedule

# Resume a Schedule
Source: https://upstash.com/docs/qstash/api-reference/schedules/resume-a-schedule

/qstash/openapi.yaml post /v2/schedules/{scheduleId}/resume
Resume a paused Schedule

# Get Signing Keys
Source: https://upstash.com/docs/qstash/api-reference/signing-keys/get-signing-keys

/qstash/openapi.yaml get /v2/keys
Retrieve your current and next signing keys

# Rotate Signing Keys
Source: https://upstash.com/docs/qstash/api-reference/signing-keys/rotate-signing-keys

/qstash/openapi.yaml post /v2/keys/rotate
Rotate your signing keys

# Delete a URL Group
Source: https://upstash.com/docs/qstash/api-reference/url-groups/delete-a-url-group

/qstash/openapi.yaml delete /v2/topics/{urlGroupName}
Delete a topic and all its endpoints

# Get a URL Group
Source: https://upstash.com/docs/qstash/api-reference/url-groups/get-a-url-group

/qstash/openapi.yaml get /v2/topics/{urlGroupName}
Retrieve details of a specific URL Group

# List URL Groups
Source: https://upstash.com/docs/qstash/api-reference/url-groups/list-url-groups

/qstash/openapi.yaml get /v2/topics
List all your URL Groups

# Remove Endpoints
Source: https://upstash.com/docs/qstash/api-reference/url-groups/remove-endpoints

/qstash/openapi.yaml delete /v2/topics/{urlGroupName}/endpoints
Remove one or more endpoints from a URL Group

# Upsert URL Group and Endpoint
Source: https://upstash.com/docs/qstash/api-reference/url-groups/upsert-url-group-and-endpoint

/qstash/openapi.yaml post /v2/topics/{urlGroupName}/endpoints
Add an endpoint to a URL Group

# API Rate Limit Response
Source: https://upstash.com/docs/qstash/api/api-ratelimiting

## Overview

There is no request per second limit for operational API's as listed below:

* trigger, publish, enqueue, notify, wait, batch
* Other endpoints (like logs,listing flow-controls, queues, schedules etc) have rps limit. This is a short-term limit **per second** to prevent rapid bursts of requests.

**Headers**:

* `Burst-RateLimit-Limit`: Maximum number of requests allowed in the burst window (1 second)
* `Burst-RateLimit-Remaining`: Remaining number of requests in the burst window (1 second)
* `Burst-RateLimit-Reset`: Time (in unix timestamp) when the burst limit will reset

### Example Rate Limit Error Handling

```typescript Handling Daily Rate Limit Error
import { QstashDailyRatelimitError } from "@upstash/qstash";

try {
  // Example of a publish request that could hit the daily rate limit
  const result = await client.publishJSON({
    url: "https://my-api...",
    // or urlGroup: "the name or id of a url group"
    body: {
      hello: "world",
    },
  });
} catch (error) {
  if (error instanceof QstashDailyRatelimitError) {
    console.log("Daily rate limit exceeded. Retry after:", error.reset);
    // Implement retry logic or notify the user
  } else {
    console.error("An unexpected error occurred:", error);
  }
}
```

```typescript Handling Burst Rate Limit Error
import { QstashRatelimitError } from "@upstash/qstash";

try {
  // Example of a request that could hit the burst rate limit
  const result = await client.publishJSON({
    url: "https://my-api...",
    // or urlGroup: "the name or id of a url group"
    body: {
      hello: "world",
    },
  });
} catch (error) {
  if (error instanceof QstashRatelimitError) {
    console.log("Burst rate limit exceeded. Retry after:", error.reset);
    // Implement exponential backoff or delay before retrying
  } else {
    console.error("An unexpected error occurred:", error);
  }
}
```

# Authentication
Source: https://upstash.com/docs/qstash/api/authentication

You'll need to authenticate your requests to access any of the endpoints in the
QStash API. In this guide, we'll look at how authentication works.

## Bearer Token

When making requests to QStash, you will need your `QSTASH_TOKEN` — you will
find it in the [console](https://console.upstash.com/qstash). Here's how to add
the token to the request header using cURL:

```bash
curl https://qstash.upstash.io/v2/publish/... \
  -H "Authorization: Bearer <QSTASH_TOKEN>"
```

## Query Parameter

In environments where setting the header is not possible, you can use the `qstash_token` query parameter instead.

```bash
curl https://qstash.upstash.io/v2/publish/...?qstash_token=<QSTASH_TOKEN>
```

Always keep your token safe and reset it if you suspect it has been compromised.

# At-Least-Once Delivery
Source: https://upstash.com/docs/qstash/features/at-least-once

QStash provides at-least-once delivery for all messages. This guarantees that no messages will be lost, even in the face of server crashes, or other unexpected problems.

In normal operation, each message is delivered once, excluding retries. However, in rare cases, QStash may deliver the same message more than once, even if your endpoint has already processed it successfully. This can happen when QStash cannot reliably determine whether the previous delivery attempt completed, so it retries the message to preserve at-least-once delivery guarantees.

A duplicate delivery can happen in a flow like this:

1. A message is published to QStash.
2. QStash attempts to deliver the message to the destination.
3. Before the request is completed, the QStash server shuts down unexpectedly.
4. When the server restarts, it cannot determine the final delivery status of the message.
5. To avoid losing the message, QStash delivers it again.

<Info>
  True exactly-once delivery cannot be guaranteed in distributed systems under all failure scenarios.

  Most production messaging systems therefore use at-least-once delivery together with idempotent handlers to prioritize reliability and prevent message loss.

  To learn more about the underlying coordination challenge, see the [Two Generals' Problem](https://en.wikipedia.org/wiki/Two_Generals%27_Problem).
</Info>

There are three common strategies to handle duplicate deliveries:

#### 1. Use an idempotency key

Because duplicate deliveries can occur, you can use an idempotency key to ensure that the an operation is executed only once.

Each QStash message includes a unique `Upstash-Message-Id` header, which you can use for this purpose.

For example, if your handler updates a database record, you can store the `Upstash-Message-Id` in the database along with the record. Before processing a message, you can check if the `Upstash-Message-Id` has already been processed. If it has, you can skip processing the message again.

An example implementation using Redis is shown below:

```typescript title="api/handler/route.ts"
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

export async function GET(request: Request): Promise<Response> {
  const messageId = request.headers.get("Upstash-Message-Id");

  const isNew = await redis.set(`processed:${messageId}`, "true", {
    nx: true,
    ex: 60 * 60 * 24,
  });

  if (!isNew) {
    return Response.json({ message: "Message already processed" }, { status: 200 });
  }

  // critical business logic here

  return Response.json({ message: "Message processed" }, { status: 200 });
}
```

#### 2. Design idempotent operations

You can also design your system so that applying the same operation multiple times does not change the final state. In this case, you may not need to store a separate idempotency key.

For example, if your handler sets a field to true, processing the same message multiple times has the same effect as processing it once. The final state remains true.

#### 3. Accept duplicates

In some cases, duplicate messages may be acceptable.

For example, if a message triggers a non-critical notification email, receiving the same message more than once may be tolerable, even if it results in multiple emails being sent.

This approach is only recommended when duplicate processing does not affect correctness, or any other critical behavior.

# Background Jobs
Source: https://upstash.com/docs/qstash/features/background-jobs

## When do you need background jobs
Background jobs are essential for executing tasks that are too time-consuming to run in the
main execution thread without affecting the user experience.

These tasks might include data processing, sending batch emails, performing scheduled maintenance,
or any other operations that are not immediately required to respond to user requests.

Utilizing background jobs allows your application to remain responsive and scalable, handling more requests simultaneously by offloading
heavy lifting to background processes.

<Note>
In Serverless frameworks, your hosting provider will likely have a limit for how long each task can last. Try searching
for the maximum execution time for your hosting provider to find out more.
</Note>

## How to use QStash for background jobs
QStash provides a simple and efficient way to run background jobs, you can understand it as a 2 step process:

1. **Public API** Create a public API endpoint within your application. The endpoint should contain the logic for the background job.
<Warning>
QStash requires a public endpoint to trigger background jobs, which means it cannot directly access localhost APIs.
To get around this, you have two options:
* Run QStash [development server](/docs/qstash/howto/local-development) locally
* Set up a [local tunnel](/docs/qstash/howto/local-tunnel) for your API
</Warning>
2. **QStash Request** Invoke QStash to start/schedule the execution of the API endpoint.

Here's what this looks like in a simple Next.js application:

<CodeGroup>
  ```tsx app/page.tsx
  "use client"

  export default function Home() {
    async function handleClick() {
      // Send a request to our server to start the background job.
      // For proper error handling, refer to the quick start.
      // Note: This can also be a server action instead of a route handler
      await fetch("/api/start-email-job", {
        method: "POST",
        body: JSON.stringify({
          users: ["a@gmail.com", "b@gmail.com", "c.gmail.com"]
        }),
      })

    }

    return (
      <main>
        <button onClick={handleClick}>Run background job</button>
      </main>
    );
  }
  ```

  ```typescript app/api/start-email-job/route.ts
  import { Client } from "@upstash/qstash";

  const qstashClient = new Client({
    token: "YOUR_TOKEN",
  });

  export async function POST(request: Request) {
    const body = await request.json();
    const users: string[] = body.users;
    // If you know the public URL of the email API, you can use it directly
    const rootDomain = request.url.split('/').slice(0, 3).join('/');
    const emailAPIURL = `${rootDomain}/api/send-email`; // ie: https://yourapp.com/api/send-email

    // Tell QStash to start the background job.
    // For proper error handling, refer to the quick start.
    await qstashClient.publishJSON({
      url: emailAPIURL,
      body: {
        users
      }
    });

    return new Response("Job started", { status: 200 });
  }

  ```

  ```typescript app/api/send-email/route.ts
  // This is a public API endpoint that will be invoked by QStash.
  // It contains the logic for the background job and may take a long time to execute.
  import { sendEmail } from "your-email-library";

  export async function POST(request: Request) {
    const body = await request.json();
    const users: string[] = body.users;

    // Send emails to the users
    for (const user of users) {
      await sendEmail(user);
    }

    return new Response("Job started", { status: 200 });
  }
  ```
</CodeGroup>

To better understand the application, let's break it down:

1. **Client**: The client application contains a button that, when clicked, sends a request to the server to start the background job.
2. **Next.js server**: The first endpoint, `/api/start-email-job`, is invoked by the client to start the background job.
3. **QStash**: The QStash client is used to invoke the `/api/send-email` endpoint, which contains the logic for the background job.

Here is a visual representation of the process:

  <img className="block dark:hidden" alt="Background job diagram" />
  <img className="hidden dark:block" alt="Background job diagram" />

To view a more detailed Next.js quick start guide for setting up QStash, refer to the [quick start](/docs/qstash/quickstarts/vercel-nextjs) guide.

It's also possible to schedule a background job to run at a later time using [schedules](/docs/qstash/features/schedules).

If you'd like to invoke another endpoint when the background job is complete, you can use [callbacks](/docs/qstash/features/callbacks).

# Batching
Source: https://upstash.com/docs/qstash/features/batch

[Publishing](/docs/qstash/howto/publishing) is great for sending one message
at a time, but sometimes you want to send a batch of messages at once.

This can be useful to send messages to a single or multiple destinations.
QStash provides the `batch` endpoint to help
you with this.

If the format of the messages are valid, the response will be an array of
responses for each message in the batch. When batching URL Groups, the response
will be an array of responses for each destination in the URL Group. If one
message fails to be sent, that message will have an error response, but the
other messages will still be sent.

<Note>You can publish to destination, URL Group or queue in the same batch request.</Note>

## Batching messages with destinations

<Info>You can also send messages to the same destination!</Info>

<CodeGroup>
```shell cURL
curl -XPOST https://qstash.upstash.io/v2/batch \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -d '
     [
      {
        "destination": "https://example.com/destination1"
      },
      {
        "destination": "https://example.com/destination2"
      }
     ]'
```

```typescript TypeScript
import { Client } from "@upstash/qstash";

// Each message is the same as the one you would send with the publish endpoint
const client = new Client({ token: "<QSTASH_TOKEN>" });
const res = await client.batchJSON([
  {
    url: "https://example.com/destination1",
  },
  {
    url: "https://example.com/destination2",
  },
]);
```

```python Python
from qstash import QStash

client = QStash("<QSTASH-TOKEN>")
client.message.batch_json(
    [
        {"url": "https://example.com/destination1"},
        {"url": "https://example.com/destination2"},
    ]
)
```

</CodeGroup>

## Batching messages with URL Groups

If you have a [URL Group](/docs/qstash/howto/url-group-endpoint), you can batch send with
the URL Group as well.

<CodeGroup>
```shell cURL
curl -XPOST https://qstash.upstash.io/v2/batch \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -d '
     [
      {
        "destination": "myUrlGroup"
      },
      {
        "destination": "https://example.com/destination2"
      }
     ]'
```

```typescript TypeScript
const client = new Client({ token: "<QSTASH_TOKEN>" });

// Each message is the same as the one you would send with the publish endpoint
const res = await client.batchJSON([
  {
    urlGroup: "myUrlGroup",
  },
  {
    url: "https://example.com/destination2",
  },
]);
```

```python Python
from qstash import QStash

client = QStash("<QSTASH-TOKEN>")
client.message.batch_json(
    [
        {"url_group": "my-url-group"},
        {"url": "https://example.com/destination2"},
    ]
)
```

</CodeGroup>

## Batching messages with queue

If you have a [queue](/docs/qstash/features/queues), you can batch send with
the queue. It is the same as publishing to a destination, but you need to set the queue name.

<CodeGroup>
```shell cURL
curl -XPOST https://qstash.upstash.io/v2/batch \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -d '
     [
      {
        "queue": "my-queue",
        "destination": "https://example.com/destination1"
      },
      {
        "queue": "my-second-queue",
        "destination": "https://example.com/destination2"
      }
     ]'
```

```typescript TypeScript
const client = new Client({ token: "<QSTASH_TOKEN>" });

const res = await client.batchJSON([
  {
    queueName: "my-queue",
    url: "https://example.com/destination1",
  },
  {
    queueName: "my-second-queue",
    url: "https://example.com/destination2",
  },
]);
```

```python Python
from upstash_qstash import QStash
from upstash_qstash.message import BatchRequest

qstash = QStash("<QSTASH_TOKEN>")

messages = [
    BatchRequest(
        queue="my-queue",
        url="https://httpstat.us/200",
        body=f"hi 1",
        retries=0
    ),
    BatchRequest(
        queue="my-second-queue",
        url="https://httpstat.us/200",
        body=f"hi 2",
        retries=0
    ),
]

qstash.message.batch(messages)
```

</CodeGroup>

## Batching messages with headers and body

You can provide custom headers and a body for each message in the batch.

<CodeGroup>
```shell cURL
curl -XPOST https://qstash.upstash.io/v2/batch   -H "Authorization: Bearer XXX" \
    -H "Content-Type: application/json" \
    -d '
    [
      {
          "destination": "myUrlGroup",
          "headers":{
            "Upstash-Delay":"5s",
            "Upstash-Forward-Hello":"123456"
          },
          "body": "Hello World"
      },
      {
          "destination": "https://example.com/destination1",
          "headers":{
            "Upstash-Delay":"7s",
            "Upstash-Forward-Hello":"789"
          }
      },
      {
          "destination": "https://example.com/destination2",
          "headers":{
            "Upstash-Delay":"9s",
            "Upstash-Forward-Hello":"again"
          }
      }
    ]'
```

```typescript TypeScript
const client = new Client({ token: "<QSTASH_TOKEN>" });

// Each message is the same as the one you would send with the publish endpoint
const msgs = [
  {
    urlGroup: "myUrlGroup",
    delay: 5,
    body: "Hello World",
    headers: {
      hello: "123456",
    },
  },
  {
    url: "https://example.com/destination1",
    delay: 7,
    headers: {
      hello: "789",
    },
  },
  {
    url: "https://example.com/destination2",
    delay: 9,
    headers: {
      hello: "again",
    },
    body: {
      Some: "Data",
    },
  },
];

const res = await client.batchJSON(msgs);
```

```python Python
from qstash import QStash

client = QStash("<QSTASH-TOKEN>")
client.message.batch_json(
    [
        {
            "url_group": "my-url-group",
            "delay": "5s",
            "body": {"hello": "world"},
            "headers": {"random": "header"},
        },
        {
            "url": "https://example.com/destination1",
            "delay": "1m",
        },
        {
            "url": "https://example.com/destination2",
            "body": {"hello": "again"},
        },
    ]
)
```

</CodeGroup>

#### The response for this will look like

```json
[
  [
    {
      "messageId": "msg_...",
      "url": "https://myUrlGroup-endpoint1.com"
    },
    {
      "messageId": "msg_...",
      "url": "https://myUrlGroup-endpoint2.com"
    }
  ],
  {
    "messageId": "msg_..."
  },
  {
    "messageId": "msg_..."
  }
]
```

# Callbacks
Source: https://upstash.com/docs/qstash/features/callbacks

All serverless function providers have a maximum execution time for each
function. Usually you can extend this time by paying more, but it's still
limited. QStash provides a way to go around this problem by using callbacks.

## What is a callback?

A callback allows you to call a long running function without having to wait for
its response. Instead of waiting for the request to finish, you can add a
callback url to your published message and we will call your callback URL with the response.

<Note>
Note that the callback might be called multiple times for each retry until the endpoint
returns success(status code 2XX) or retries are exhausted. You can assert that retries
are exhausted via `callbackBody.retried == callbackBody.maxRteries`. See the complete
callback body json below.
</Note>

<img className="block h-32 dark:hidden" />
<img className="hidden h-40 dark:block" />

1. You publish a message to QStash using the `/v2/publish` endpoint
2. QStash will enqueue the message and deliver it to the destination
3. QStash waits for the response from the destination
4. When the response is ready, QStash calls your callback URL with the response

Callbacks publish a new message with the response to the callback URL. Messages
created by callbacks are charged as any other message.

## How do I use Callbacks?

You can add a callback url in the `Upstash-Callback` header when publishing a
message. The value must be a valid URL.

<CodeGroup>
```bash cURL
curl -X POST \
  https://qstash.upstash.io/v2/publish/https://my-api... \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <QSTASH_TOKEN>' \
  -H 'Upstash-Callback: <CALLBACK_URL>' \
  -d '{ "hello": "world" }'
```

```typescript Typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const res = await client.publishJSON({
  url: "https://my-api...",
  body: { hello: "world" },
  callback: "https://my-callback...",
});
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://my-api...",
    body={
        "hello": "world",
    },
    callback="https://my-callback...",
)
```
</CodeGroup>

The callback body sent to you will be a JSON object with the following fields:

```json
{
  "status": 200,
  "header": { "key": ["value"] },         // Response header
  "body": "YmFzZTY0IGVuY29kZWQgcm9keQ==", // base64 encoded response body
  "retried": 2,                           // How many times we retried to deliver the original message
  "maxRetries": 3,                        // Number of retries before the message assumed to be failed to delivered.
  "sourceMessageId": "msg_xxx",           // The ID of the message that triggered the callback
  "topicName": "myTopic",                 // The name of the URL Group (topic) if the request was part of a URL Group
  "endpointName": "myEndpoint",           // The endpoint name if the endpoint is given a name within a topic
  "url": "http://myurl.com",              // The destination url of the message that triggered the callback
  "method": "GET",                        // The http method of the message that triggered the callback
  "sourceHeader": { "key": "value" },     // The http header of the message that triggered the callback
  "sourceBody": "YmFzZTY0kZWQgcm9keQ==",  // The base64 encoded body of the message that triggered the callback
  "notBefore": "1701198458025",           // The unix timestamp of the message that triggered the callback is/will be delivered in milliseconds
  "createdAt": "1701198447054",           // The unix timestamp of the message that triggered the callback is created in milliseconds
  "scheduleId": "scd_xxx",                // The scheduleId of the message if the message is triggered by a schedule
  "callerIP": "178.247.74.179"            // The IP address where the message that triggered the callback is published from
}
```

In Next.js you could use the following code to handle the callback:

```js
// pages/api/callback.js

import { verifySignature } from "@upstash/qstash/nextjs";

function handler(req, res) {
  // responses from qstash are base64-encoded
  const decoded = atob(req.body.body);
  console.log(decoded);

  return res.status(200).end();
}

export default verifySignature(handler);

export const config = {
  api: {
    bodyParser: false,
  },
};
```

We may truncate the response body if it exceeds your plan limits. You can check
your `Max Message Size` in the
[console](https://console.upstash.com/qstash?tab=details).

Make sure you verify the authenticity of the callback request made to your API
by
[verifying the signature](/docs/qstash/features/security/#request-signing-optional).

# What is a Failure-Callback?

Failure callbacks are similar to callbacks but they are called only when all the retries are exhausted and still
the message can not be delivered to the given endpoint.

This is designed to be an serverless alternative to [List messages to DLQ](/docs/qstash/api-reference/dlq/list-dlq-messages).

You can add a failure callback URL in the `Upstash-Failure-Callback` header when publishing a
message. The value must be a valid URL.

<CodeGroup>
```bash cURL
curl -X POST \
  https://qstash.upstash.io/v2/publish/<DESTINATION_URL> \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <QSTASH_TOKEN>' \
  -H 'Upstash-Failure-Callback: <CALLBACK_URL>' \
  -d '{ "hello": "world" }'
```

```typescript Typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const res = await client.publishJSON({
  url: "https://my-api...",
  body: { hello: "world" },
  failureCallback: "https://my-callback...",
});
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://my-api...",
    body={
        "hello": "world",
    },
    failure_callback="https://my-callback...",
)
```
</CodeGroup>

The callback body sent to you will be a JSON object with the following fields:

```json
{
  "status": 400,
  "header": { "key": ["value"] },         // Response header
  "body": "YmFzZTY0IGVuY29kZWQgcm9keQ==", // base64 encoded response body
  "retried": 3,                           // How many times we retried to deliver the original message
  "maxRetries": 3,                        // Number of retries before the message assumed to be failed to delivered.
  "dlqId": "1725323658779-0",             // Dead Letter Queue id. This can be used to retrieve/remove the related message from DLQ.
  "sourceMessageId": "msg_xxx",           // The ID of the message that triggered the callback
  "topicName": "myTopic",                 // The name of the URL Group (topic) if the request was part of a topic
  "endpointName": "myEndpoint",           // The endpoint name if the endpoint is given a name within a topic
  "url": "http://myurl.com",              // The destination url of the message that triggered the callback
  "method": "GET",                        // The http method of the message that triggered the callback
  "sourceHeader": { "key": "value" },     // The http header of the message that triggered the callback
  "sourceBody": "YmFzZTY0kZWQgcm9keQ==",  // The base64 encoded body of the message that triggered the callback
  "notBefore": "1701198458025",           // The unix timestamp of the message that triggered the callback is/will be delivered in milliseconds
  "createdAt": "1701198447054",           // The unix timestamp of the message that triggered the callback is created in milliseconds
  "scheduleId": "scd_xxx",                // The scheduleId of the message if the message is triggered by a schedule
  "callerIP": "178.247.74.179"            // The IP address where the message that triggered the callback is published from
}
```

You can also use a callback and failureCallback together!

## Configuring Callbacks

Publishes/enqueues for callbacks can also be configured with the same HTTP headers that are used to configure direct publishes/enqueues.

<Tip> You can refer to headers that are used to configure `publishes` [here](/docs/qstash/api-reference/messages/publish-a-message) and for `enqueues`
[here](/docs/qstash/api-reference/messages/enqueue-a-message) </Tip>

Instead of the `Upstash` prefix for headers, the `Upstash-Callback`/`Upstash-Failure-Callback` prefix can be used to configure callbacks as follows:

```
Upstash-Callback-Timeout
Upstash-Callback-Retries
Upstash-Callback-Delay
Upstash-Callback-Method
Upstash-Failure-Callback-Timeout
Upstash-Failure-Callback-Retries
Upstash-Failure-Callback-Delay
Upstash-Failure-Callback-Method
```

You can also forward headers to your callback endpoints as follows:
```
Upstash-Callback-Forward-MyCustomHeader
Upstash-Failure-Callback-Forward-MyCustomHeader
```

# Deduplication
Source: https://upstash.com/docs/qstash/features/deduplication

Messages can be deduplicated to prevent duplicate messages from being sent. When
a duplicate message is detected, it is accepted by QStash but not enqueued. This
can be useful when the connection between your service and QStash fails, and you
never receive the acknowledgement. You can simply retry publishing and can be
sure that the message will enqueued only once.

In case a message is a duplicate, we will accept the request and return the
messageID of the existing message. The only difference will be the response
status code. We'll send HTTP `202 Accepted` code in case of a duplicate message.

## Deduplication ID

To deduplicate a message, you can send the `Upstash-Deduplication-Id` header
when publishing the message.

<CodeGroup>
```shell cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -H "Upstash-Deduplication-Id: abcdef" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://my-api..."'
```

```typescript TypeScript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const res = await client.publishJSON({
  url: "https://my-api...",
  body: { hello: "world" },
  deduplicationId: "abcdef",
});
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://my-api...",
    body={
        "hello": "world",
    },
    deduplication_id="abcdef",
)
```
</CodeGroup>

## Content Based Deduplication

If you want to deduplicate messages automatically, you can set the
`Upstash-Content-Based-Deduplication` header to `true`.

<CodeGroup>
```shell cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -H "Upstash-Content-Based-Deduplication: true" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/...'
```

```typescript TypeScript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const res = await client.publishJSON({
  url: "https://my-api...",
  body: { hello: "world" },
  contentBasedDeduplication: true,
});
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://my-api...",
    body={
        "hello": "world",
    },
    content_based_deduplication=True,
)
```
</CodeGroup>

Content based deduplication creates a unique deduplication ID for the message
based on the following fields:

* **Destination**: The URL Group or endpoint you are publishing the message to.

* **Body**: The body of the message.

* **Header**: This includes the `Content-Type` header and all headers, that you
  forwarded with the `Upstash-Forward-` prefix. See
  [custom HTTP headers section](/docs/qstash/howto/publishing#sending-custom-http-headers).

<Info>
The deduplication window is 10 minutes. After that, messages with the same ID or content can be sent again.
</Info>

# Delay
Source: https://upstash.com/docs/qstash/features/delay

When publishing a message, you can delay it for a certain amount of time before
it will be delivered to your API. See the [pricing table](https://upstash.com/pricing/qstash) for more information

<Warning>
For free: The maximum allowed delay is  **7 days**.

For pay-as-you-go: The maximum allowed delay is  **1 year**.

For fixed pricing: The maximum allowed delay is  **Custom(you may delay as much as needed)**.
</Warning>

## Relative Delay

Delay a message by a certain amount of time relative to the time the message was
published.

The format for the duration is `<number><unit>`. Here are some examples:

* `10s` = 10 seconds
* `1m` = 1 minute
* `30m` = half an hour
* `2h` = 2 hours
* `7d` = 7 days

You can send this duration inside the `Upstash-Delay` header.

<CodeGroup>
```shell cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -H "Upstash-Delay: 1m" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://my-api...'
```

```typescript Typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const res = await client.publishJSON({
  url: "https://my-api...",
  body: { hello: "world" },
  delay: 60,
});
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://my-api...",
    body={
        "hello": "world",
    },
    headers={
        "test-header": "test-value",
    },
    delay="60s",
)
```
</CodeGroup>

<Warning>

`Upstash-Delay` will get overridden by `Upstash-Not-Before` header when both are
used together.

</Warning>

## Absolute Delay

Delay a message until a certain time in the future. The format is a unix
timestamp in seconds, based on the UTC timezone.

You can send the timestamp inside the `Upstash-Not-Before` header.

<CodeGroup>
```shell cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -H "Upstash-Not-Before: 1657104947" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://my-api...'
```

```typescript Typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const res = await client.publishJSON({
  url: "https://my-api...",
  body: { hello: "world" },
  notBefore: 1657104947,
});
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://my-api...",
    body={
        "hello": "world",
    },
    headers={
        "test-header": "test-value",
    },
    not_before=1657104947,
)
```
</CodeGroup>

<Info>

`Upstash-Not-Before` will override the `Upstash-Delay` header when both are used
together.

</Info>

## Delays in Schedules

Adding a delay in schedules is only possible via `Upstash-Delay`. The
delay will affect the messages that will be created by the schedule and not the
schedule itself.

For example when you create a new schedule with a delay of `30s`, the messages
will be created when the schedule triggers but only delivered after 30 seconds.

# Dead Letter Queues
Source: https://upstash.com/docs/qstash/features/dlq

At times, your API may fail to process a request. This could be due to a bug in your code, a temporary issue with a third-party service, or even network issues.
QStash automatically retries messages that fail due to a temporary issue but eventually stops and moves the message to a dead letter queue to be handled manually.

Read more about retries [here](/docs/qstash/features/retry).

## How to Use the Dead Letter Queue

You can manually republish messages from the dead letter queue in the console.

  <img />

1. **Retry** - Republish the message and remove it from the dead letter queue. Republished messages are just like any other message and will be retried automatically if they fail.
2. **Delete** - Delete the message from the dead letter queue.

## Limitations

Dead letter queues are subject only to a retention period that depends on your plan. Messages are deleted when their retention period expires. See the “Max DLQ Retention” row on the [QStash Pricing](https://upstash.com/pricing/qstash) page.

# Flow Control
Source: https://upstash.com/docs/qstash/features/flowcontrol

Flow Control enables you to limit the number of messages sent to your endpoint via delaying the delivery.

* [Rate](#rate-and-period-parameters): You can specify a maximum number of calls that can be made to your endpoint within a certain time period.
* [Parallelism](#parallelism-limit): You can set a limit on the number of concurrent calls to your endpoint.

You can use either of these limits or combine them to have more control over the flow of messages to your endpoint.

If any of the limits is exceeded, additional messages will be added to waitlist and delivered once either the time period has passed (for rate limit) or the number of active calls drops below the limit (for parallelism limit).

<Steps>
  <Step title="Choose Flow Control Key">
    To use flow control, you need to choose a key first. This key is used to count the number of calls made to your endpoint.

    The limits are applied per flow-control key, not per URL. This means that you can use the same key for different URLs to apply the same limits to them.

    <Info>There are no limits to number of keys you can use.</Info>

  </Step>
  <Step title="Decide Limits">
    Decide which limits you want to apply. You can choose to apply only rate limit, only parallelism limit, or both.

    For instance, if you want to limit the number of calls to 10 per minute, you can set the rate to 10 and the period to 1 minute. If you want to limit the number of concurrent calls to 5, you can set the parallelism limit to 5.
  </Step>
  <Step title="Send a Message">
    <CodeGroup>
    ```typescript TypeScript
    const client = new Client({ token: "<QSTASH_TOKEN>" });

    await client.publishJSON({
        url: "https://example.com",
        body: { hello: "world" },
        flowControl: { key: "USER_GIVEN_KEY", parallelism: 5, rate: 10, period: "1m" },
    });
    ```

    ```bash cURL
    curl -XPOST -H 'Authorization: Bearer XXX' \
                -H "Content-type: application/json" \
                -H "Upstash-Flow-Control-Key:USER_GIVEN_KEY"  \
                -H "Upstash-Flow-Control-Value:parallelism=5,rate=10,period=1m" \
                'https://qstash.upstash.io/v2/publish/https://example.com' \
                -d '{"message":"Hello, World!"}'
    ```
    </CodeGroup>
    🎉 That's it! From now on, QStash will enforce these limits by counting the number of messages associated with this flow-control key.
  </Step>
</Steps>

## Rate and Period Parameters

The `rate` parameter specifies the maximum number of calls allowed within a given period. The `period` parameter allows you to specify the time window over which the rate limit is enforced. By default, the period is set to 1 second, but you can adjust it to control how frequently calls are allowed. For example, you can set a rate of 10 calls per minute as follows:

<CodeGroup>
```typescript TypeScript
const client = new Client({ token: "<QSTASH_TOKEN>" });

await client.publishJSON({
    url: "https://example.com",
    body: { hello: "world" },
    flowControl: { key: "USER_GIVEN_KEY", rate: 10, period: "1m" },
});
```

```bash cURL
curl -XPOST -H 'Authorization: Bearer XXX' \
            -H "Content-type: application/json" \
            -H "Upstash-Flow-Control-Key:USER_GIVEN_KEY"  \
            -H "Upstash-Flow-Control-Value:rate=10,period=1m" \
           'https://qstash.upstash.io/v2/publish/https://example.com' \
            -d '{"message":"Hello, World!"}'
```
</CodeGroup>

## Parallelism Limit

The parallelism limit is the number of calls that can be active at the same time.
Active means that the call is made to your endpoint and the response is not received yet.

You can set the parallelism limit to 10 calls active at the same time as follows:

<CodeGroup>
```typescript TypeScript
const client = new Client({ token: "<QSTASH_TOKEN>" });

await client.publishJSON({
    url: "https://example.com",
    body: { hello: "world" },
    flowControl: { key: "USER_GIVEN_KEY", parallelism: 10 },
});
```

```bash cURL
curl -XPOST -H 'Authorization: Bearer XXX' \
            -H "Content-type: application/json" \
            -H "Upstash-Flow-Control-Key:USER_GIVEN_KEY"  \
            -H "Upstash-Flow-Control-Value:parallelism=10" \
           'https://qstash.upstash.io/v2/publish/https://example.com' \
            -d '{"message":"Hello, World!"}'
```
</CodeGroup>

You can also use the Rest API to get information how many messages waiting for parallelism limit.
See the [API documentation](/docs/qstash/api-reference/flow-control/get-flow-control-key) for more details.

## Rate, Parallelism, and Period Together

All three parameters can be combined. For example, with a rate of 10 per minute, parallelism of 20, and a period of 1 minute, QStash will trigger 10 calls in the first minute and another 10 in the next. Since none of them will have finished, the system will wait until one completes before triggering another.

<CodeGroup>
```typescript TypeScript
const client = new Client({ token: "<QSTASH_TOKEN>" });

await client.publishJSON({
    url: "https://example.com",
    body: { hello: "world" },
    flowControl: { key: "USER_GIVEN_KEY", rate: 10, parallelism: 20, period: "1m" },
});
```

```bash cURL
curl -XPOST -H 'Authorization: Bearer XXX' \
            -H "Content-type: application/json" \
            -H "Upstash-Flow-Control-Key:USER_GIVEN_KEY"  \
            -H "Upstash-Flow-Control-Value:rate=10,parallelism=20,period=1m" \
           'https://qstash.upstash.io/v2/publish/https://example.com' \
            -d '{"message":"Hello, World!"}'
```
</CodeGroup>

## Management API

You can inspect flow control keys programmatically using the `flowControl` namespace on the client.

### Get a single flow control key

Returns the current state and metrics for one flow control key.

<CodeGroup>
```typescript TypeScript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });

const info = await client.flowControl.get("USER_GIVEN_KEY");
console.log(info);
// {
//   flowControlKey: "USER_GIVEN_KEY",
//   waitListSize: 5,
//   parallelismMax: 10,
//   parallelismCount: 3,
//   rateMax: 100,
//   rateCount: 42,
//   ratePeriod: 60,
//   ratePeriodStart: 1708000000,
//   isPaused: false,
//   isPinnedParallelism: false,
//   isPinnedRate: false
// }
```

```python Python
from qstash import QStash

client = QStash("<QSTASH-TOKEN>")

info = client.flow_control.get("USER_GIVEN_KEY")
print(info)
# FlowControlInfo(
#   key="USER_GIVEN_KEY",
#   wait_list_size=5,
#   parallelism_max=10,
#   parallelism_count=3,
#   rate_max=100,
#   rate_count=42,
#   rate_period=60,
#   rate_period_start=1708000000,
#   is_paused=False,
#   is_pinned_parallelism=False,
#   is_pinned_rate=False
# )
```

```bash cURL
curl -X GET https://qstash.upstash.io/v2/flowControl/USER_GIVEN_KEY \
     -H "Authorization: Bearer <token>"
```
</CodeGroup>

The response fields are:

| Field | Description |
|---|---|
| `flowControlKey` | The flow control key name |
| `waitListSize` | Number of messages currently waiting in the queue |
| `parallelismMax` | Configured maximum concurrent messages (if set) |
| `parallelismCount` | Number of messages currently running in parallel |
| `rateMax` | Configured maximum messages per rate period (if set) |
| `rateCount` | Number of messages dispatched in the current rate period |
| `ratePeriod` | Rate period length in seconds |
| `ratePeriodStart` | Unix timestamp when the current rate period started |
| `isPaused` | Whether delivery is currently paused for this key |
| `isPinnedParallelism` | Whether the parallelism configuration is pinned |
| `isPinnedRate` | Whether the rate configuration is pinned |

### Pause and resume

You can pause delivery for a flow control key. While paused, messages are held in the waitlist and no new deliveries are made. Resume to continue delivery.

<CodeGroup>
```typescript TypeScript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });

// Pause delivery
await client.flowControl.pause("USER_GIVEN_KEY");

// Check status
const info = await client.flowControl.get("USER_GIVEN_KEY");
console.log(info.isPaused); // true

// Resume delivery
await client.flowControl.resume("USER_GIVEN_KEY");
```

```python Python
from qstash import QStash

client = QStash("<QSTASH-TOKEN>")

# Pause delivery
client.flow_control.pause("USER_GIVEN_KEY")

# Check status
info = client.flow_control.get("USER_GIVEN_KEY")
print(info.is_paused)  # True

# Resume delivery
client.flow_control.resume("USER_GIVEN_KEY")
```

```bash cURL
# Pause delivery
curl -X POST https://qstash.upstash.io/v2/flowControl/USER_GIVEN_KEY/pause \
     -H "Authorization: Bearer <token>"

# Resume delivery
curl -X POST https://qstash.upstash.io/v2/flowControl/USER_GIVEN_KEY/resume \
     -H "Authorization: Bearer <token>"
```
</CodeGroup>

### Pin and unpin

Pinning locks a flow control key's configuration so that incoming messages cannot override it. This is useful when you want to enforce a fixed rate or parallelism regardless of what individual messages request.

<CodeGroup>
```typescript TypeScript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });

// Pin a fixed configuration
await client.flowControl.pin("USER_GIVEN_KEY", {
  parallelism: 3,
  rate: 20,
  period: 120, // seconds
});

// Check pinned status
const info = await client.flowControl.get("USER_GIVEN_KEY");
console.log(info.isPinnedParallelism); // true
console.log(info.isPinnedRate);        // true

// Unpin specific configs
await client.flowControl.unpin("USER_GIVEN_KEY", {
  parallelism: true,
  rate: true,
});
```

```python Python
from qstash import QStash

client = QStash("<QSTASH-TOKEN>")

# Pin a fixed configuration
client.flow_control.pin(
    "USER_GIVEN_KEY",
    {"parallelism": 3, "rate": 20, "period": 120},
)

# Check pinned status
info = client.flow_control.get("USER_GIVEN_KEY")
print(info.is_pinned_parallelism)  # True
print(info.is_pinned_rate)         # True

# Unpin specific configs
client.flow_control.unpin(
    "USER_GIVEN_KEY",
    {"parallelism": True, "rate": True},
)
```

```bash cURL
# Pin a fixed configuration
curl -X POST "https://qstash.upstash.io/v2/flowControl/USER_GIVEN_KEY/pin?parallelism=3&rate=20&period=120" \
     -H "Authorization: Bearer <token>"

# Unpin specific configs
curl -X POST "https://qstash.upstash.io/v2/flowControl/USER_GIVEN_KEY/unpin?parallelism=true&rate=true" \
     -H "Authorization: Bearer <token>"
```
</CodeGroup>

You can pin only parallelism, only rate, or both. When pinning rate, you can optionally include a `period` (in seconds). Similarly, you can unpin them independently.

### Reset rate

Resets the rate count and ends the current rate period for a flow control key. The next message delivery will start a fresh rate period.

<CodeGroup>
```typescript TypeScript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });

await client.flowControl.resetRate("USER_GIVEN_KEY");
```

```python Python
from qstash import QStash

client = QStash("<QSTASH-TOKEN>")

client.flow_control.reset_rate("USER_GIVEN_KEY")
```

```bash cURL
curl -X POST https://qstash.upstash.io/v2/flowControl/USER_GIVEN_KEY/resetRate \
     -H "Authorization: Bearer <token>"
```
</CodeGroup>

### Get global parallelism

Returns the global parallelism usage across all flow control keys.

<CodeGroup>
```typescript TypeScript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });

const info = await client.flowControl.getGlobalParallelism();
console.log(info);
// {
//   parallelismMax: 500,
//   parallelismCount: 42
// }
```

```python Python
from qstash import QStash

client = QStash("<QSTASH-TOKEN>")

info = client.flow_control.get_global_parallelism()
print(info)
# GlobalParallelismInfo(
#   parallelism_max=500,
#   parallelism_count=42
# )
```

```bash cURL
curl -X GET https://qstash.upstash.io/v2/globalParallelism \
     -H "Authorization: Bearer <token>"
```
</CodeGroup>

| Field | Description |
|---|---|
| `parallelismMax` | The maximum global parallelism |
| `parallelismCount` | The current number of active requests globally |

## Monitor

You can monitor wait list size of your flow control key's from the console `FlowControl` tab. The console also allows you to pin, unpin, and reset rate for flow control keys directly.

  <img />

Also you can get the same info using the REST API.
* [List All Flow Control Keys](/docs/qstash/api-reference/flow-control/list-flow-control-keys).
* [Single Flow Control Key](/docs/qstash/api-reference/flow-control/get-flow-control-key).
* [Global Parallelism](/docs/qstash/api-reference/flow-control/get-global-parallelism).

# Queues
Source: https://upstash.com/docs/qstash/features/queues

The queue concept in QStash allows ordered delivery (FIFO).
See the [API doc](/docs/qstash/api-reference/queues/list-queues) for the full list of related Rest APIs.
Here we list common use cases for Queue and how to use them.

## Ordered Delivery

With Queues, the ordered delivery is guaranteed by default.
This means:

* Your messages will be queued without blocking the REST API and sent one by one in FIFO order. Queued means [CREATED](/docs/qstash/howto/debug-logs) event will be logged.
* The next message will wait for retries of the current one if it can not be delivered because your endpoint returns non-2xx code.
  In other words, the next message will be [ACTIVE](/docs/qstash/howto/debug-logs) only after the last message is either [DELIVERED](/docs/qstash/howto/debug-logs) or
  [FAILED](/docs/qstash/howto/debug-logs).
* Next message will wait for [callbacks](/docs/qstash/features/callbacks#what-is-a-callback) or [failure callbacks](/docs/qstash/features/callbacks#what-is-a-failure-callback) to finish.

<CodeGroup>

```bash cURL
curl -XPOST -H 'Authorization: Bearer XXX' \
            -H "Content-type: application/json" \
  'https://qstash.upstash.io/v2/enqueue/my-queue/https://example.com' -d '{"message":"Hello, World!"}'
```

```typescript TypeScript
const client = new Client({ token: "<QSTASH_TOKEN>" });

const queue = client.queue({
  queueName: "my-queue"
})

await queue.enqueueJSON({
  url: "https://example.com",
  body: {
    "Hello": "World"
  }
})
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.enqueue_json(
    queue="my-queue",
    url="https://example.com",
    body={
        "Hello": "World",
    },
)
```
</CodeGroup>

## Controlled Parallelism

<Warning>
For the parallelism limit, we introduced an easier and less limited API with publish.
Please check the [Flow Control](/docs/qstash/features/flowcontrol) page for the detailed information.

Setting parallelism with queues will be deprecated at some point.
</Warning>

To ensure that your endpoint is not overwhelmed and also you want more than one-by-one delivery for better throughput,
you can achieve controlled parallelism with queues.

By default, queues have parallelism 1.
Depending on your [plan](https://upstash.com/pricing/qstash), you can configure the parallelism of your queues as follows:

<CodeGroup>

```bash cURL
curl -XPOST https://qstash.upstash.io/v2/queues/ \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "queueName": "my-queue",
    "parallelism": 5,
  }'
```

```typescript TypeScript
const client = new Client({ token: "<QSTASH_TOKEN>" });

const queue = client.queue({
  queueName: "my-queue"
})

await queue.upsert({
  parallelism: 1,
})
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.queue.upsert("my-queue", parallelism=5)
```

</CodeGroup>

After that, you can use the `enqueue` path to send your messages.

<CodeGroup>

```bash cURL
curl -XPOST -H 'Authorization: Bearer XXX' \
            -H "Content-type: application/json" \
  'https://qstash.upstash.io/v2/enqueue/my-queue/https://example.com' -d '{"message":"Hello, World!"}'
```

```typescript TypeScript
const client = new Client({ token: "<QSTASH_TOKEN>" });

const queue = QStashClient.queue({
  queueName: "my-queue"
})

await queue.enqueueJSON({
  url: "https://example.com",
  body: {
    "Hello": "World"
  }
})
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.enqueue_json(
    queue="my-queue",
    url="https://example.com",
    body={
        "Hello": "World",
    },
)
```
</CodeGroup>

You can check the parallelism of your queues with the following API:

<CodeGroup>
```bash cURL
curl https://qstash.upstash.io/v2/queues/my-queue \
  -H "Authorization: Bearer <token>"
```

```typescript TypeScript
const client = new Client({ token: "<QSTASH_TOKEN>" });

const queue = client.queue({
  queueName: "my-queue"
})

const res = await queue.get()
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.queue.get("my-queue")
```
</CodeGroup>

# Retry
Source: https://upstash.com/docs/qstash/features/retry

<Warning title="Max HTTP Response Duration">
QStash will abort a delivery attempt if **the HTTP call to your endpoint does not return within the plan-specific Max HTTP Response Duration**.
See the current limits on the <a href="https://upstash.com/pricing/qstash" target="_blank" rel="noopener">QStash pricing page</a>.
</Warning>

Many things can go wrong in a serverless environment. If your API does not
respond with a success status code (2XX), we retry the request to ensure every
message will be delivered.

By default, we retry a failed delivery 3 times. You can change this per message
by sending the `Upstash-Retries` header:

<CodeGroup>
```shell cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -H "Upstash-Retries: 2" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://my-api...'
```

```typescript TypeScript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const res = await client.publishJSON({
  url: "https://my-api...",
  body: { hello: "world" },
  retries: 2,
});
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://my-api...",
    body={
        "hello": "world",
    },
    retries=2,
)
```
</CodeGroup>

The backoff algorithm calculates the retry delay based on the number of retries.
Each delay is capped at 1 day.

```
n = how many times this request has been retried
delay =  min(86400, e ** (2.5*n)) // in seconds
```

| n   | delay  |
| --- | ------ |
| 1   | 12s    |
| 2   | 2m28s  |
| 3   | 30m8ss |
| 4   | 6h7m6s |
| 5   | 24h    |
| 6   | 24h    |

## Custom Retry Delay

You can customize the delay between retry attempts by using the `Upstash-Retry-Delay` header when publishing a message. This allows you to override the default exponential backoff with your own mathematical expressions.

<CodeGroup>
```shell cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -H "Upstash-Retries: 3" \
    -H "Upstash-Retry-Delay: pow(2, retried) * 1000" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://my-api...'
```

```typescript TypeScript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const res = await client.publishJSON({
  url: "https://my-api...",
  body: { hello: "world" },
  retries: 3,
  retryDelay: "pow(2, retried) * 1000", // 2^retried * 1000ms
});
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://my-api...",
    body={
        "hello": "world",
    },
    retries=3,
    retry_delay="pow(2, retried) * 1000",  # 2^retried * 1000ms
)
```
</CodeGroup>

The `retryDelay` expression can use mathematical functions and the special variable `retried` (current retry attempt count starting from 0).

**Supported functions:**
* `pow` - Power function
* `sqrt` - Square root
* `abs` - Absolute value
* `exp` - Exponential
* `floor` - Floor function
* `ceil` - Ceiling function
* `round` - Rounding function
* `min` - Minimum of values
* `max` - Maximum of values

**Examples:**
* `1000` - Fixed 1 second delay
* `1000 * (1 + retried)` - Linear backoff: 1s, 2s, 3s, 4s...
* `pow(2, retried) * 1000` - Exponential backoff: 1s, 2s, 4s, 8s...
* `max(1000, pow(2, retried) * 100)` - Exponential with minimum 1s delay

## Retry-After Headers

Instead of using the default backoff algorithm, you can specify when QStash should retry your message.
To do this, include one of the following headers in your response to QStash request.
* Retry-After
* X-RateLimit-Reset
* X-RateLimit-Reset-Requests
* X-RateLimit-Reset-Tokens

These headers can be set to a value in seconds, the RFC1123 date format, or a duration format (e.g., 6m5s).
For the duration format, valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".

Note that you can only delay retries up to the maximum value of the default backoff algorithm, which is one day.
If you specify a value beyond this limit, the backoff algorithm will be applied.

This feature is particularly useful if your application has rate limits, ensuring retries are scheduled appropriately without wasting attempts during restricted periods.

```
Retry-After: 0                             // Next retry will be scheduled immediately without any delay.
Retry-After: 10                            // Next retry will be scheduled after a 10-second delay.
Retry-After: 6m5s                          // Next retry will be scheduled after 6 minutes 5 seconds delay.
Retry-After: Sun, 27 Jun 2024 12:16:24 GMT // Next retry will be scheduled for the specified date, within the allowable limits.
```

## Upstash-Retried Header

QStash adds the `Upstash-Retried` header to requests sent to your API. This
indicates how many times the request has been retried.

```
Upstash-Retried: 0 // This is the first attempt
Upstash-Retried: 1 // This request has been sent once before and now is the second attempt
Upstash-Retried: 2 // This request has been sent twice before and now is the third attempt
```

## Non-Retryable Error

By default, QStash retries requests for any response that does not return a successful 2XX status code.
To explicitly disable retries for a given message, respond with a 489 status code and include the header `Upstash-NonRetryable-Error: true`.

When this header is present, QStash will immediately mark the message as failed and skip any further retry attempts. The message will then be forwarded to the Dead Letter Queue (DLQ) for manual review and resolution.

This mechanism is particularly useful in scenarios where retries are generally enabled but should be bypassed for specific known errors—such as invalid payloads or non-recoverable conditions.

# Schedules
Source: https://upstash.com/docs/qstash/features/schedules

In addition to sending a message once, you can create a schedule, and we will
publish the message in the given period. To create a schedule, you simply need
to add the `Upstash-Cron` header to your `publish` request.

Schedules can be configured using `cron` expressions.
[crontab.guru](https://crontab.guru/) is a great tool for understanding and
creating cron expressions.

By default, we evaluate cron expressions in `UTC`.
If you want to run your schedule in a specific timezone, see the section on
[Timezones](#timezones).

The following request would create a schedule that will automatically publish
the message every minute:

<CodeGroup>
```typescript Typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.schedules.create({
  destination: "https://example.com",
  cron: "* * * * *",
});
```
```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.schedule.create(
    destination="https://example.com",
    cron="* * * * *",
)
```
```shell cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -H "Upstash-Cron: * * * * *" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/schedules/https://example.com'
```
</CodeGroup>

All of the [other config options](/docs/qstash/howto/publishing#optional-parameters-and-configuration)
can still be used.

<Info>

It can take up to 60 seconds for the schedule to be loaded on an active node and
triggered for the first time.

</Info>

You can see and manage your schedules in the
[Upstash Console](https://console.upstash.com/qstash).

### Scheduling to a URL Group

Instead of scheduling a message to a specific URL, you can also create a
schedule, that publishes to a URL Group. Simply use either the URL Group name or its id:

<CodeGroup>
```typescript Typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.schedules.create({
  destination: "urlGroupName",
  cron: "* * * * *",
});
```
```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.schedule.create(
    destination="url-group-name",
    cron="* * * * *",
)
```
```bash cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -H "Upstash-Cron: * * * * *" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/schedules/<URL_GROUP_ID_OR_NAME>'
```
</CodeGroup>

### Scheduling to a Queue

You can schedule an item to be added to a queue at a specified time.

<CodeGroup>
```typescript Typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.schedules.create({
  destination: "https://example.com",
  cron: "* * * * *",
  queueName: "yourQueueName",
});
```
```bash cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -H "Upstash-Cron: * * * * *" \
    -H "Upstash-Queue-Name: yourQueueName" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/schedules/https://example.com'
```
</CodeGroup>

### Overwriting an existing schedule

You can pass scheduleId explicitly to overwrite an existing schedule or just simply create the schedule
with the given schedule id.

<CodeGroup>
```typescript Typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.schedules.create({
  destination: "https://example.com",
  scheduleId: "existingScheduleId",
  cron: "* * * * *",
});
```
```shell cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -H "Upstash-Cron: * * * * *" \
    -H "Upstash-Schedule-Id: existingScheduleId" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/schedules/https://example.com'
```
</CodeGroup>

### Timezones

By default, cron expressions are evaluated in `UTC`.
You can specify a different timezone using the `CRON_TZ` prefix directly inside
the cron expression.  All [IANA timezones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
are supported.

For example, this schedule runs every day at `04:00 AM` in New York time:

<CodeGroup>
```typescript Typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.schedules.create({
  destination: "https://example.com",
  cron: "CRON_TZ=America/New_York 0 4 * * *",
});
```
```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.schedule.create(
    destination="https://example.com",
    cron="CRON_TZ=America/New_York 0 4 * * *",
)
```
```shell cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -H "Upstash-Cron: CRON_TZ=America/New_York 0 4 * * *" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/schedules/https://example.com'
```
</CodeGroup>

# Security
Source: https://upstash.com/docs/qstash/features/security

### Request Authorization

When interacting with the QStash API, you will need an authorization token. To
get your token, go to the [QStash tab in the Console](https://console.upstash.com/qstash),
pick a region, and copy the `QSTASH_TOKEN` from the **Quickstart** section.

  <img />

Send this token along with every request made to `QStash` inside the
`Authorization` header like this:

```
"Authorization": "Bearer <QSTASH_TOKEN>"
```

### Request Signing (optional)

Because your endpoint needs to be publicly available, we recommend you verify
the authenticity of each incoming request.

#### The `Upstash-Signature` header

With each request we are sending a JWT inside the `Upstash-Signature` header.
You can learn more about them [here](https://jwt.io).

An example token would be:

**Header**

```json
{
  "alg": "HS256",
  "typ": "JWT"
}
```

**Payload**

```json
{
  "iss": "Upstash",
  "sub": "https://qstash-remote.requestcatcher.com/test",
  "exp": 1656580612,
  "nbf": 1656580312,
  "iat": 1656580312,
  "jti": "jwt_67kxXD6UBAk7DqU6hzuHMDdXFXfP",
  "body": "qK78N0k3pNKI8zN62Fq2Gm-_LtWkJk1z9ykio3zZvY4="
}
```

The JWT is signed using `HMAC SHA256` algorithm with your current signing key
and includes the following claims:

#### Claims

##### `iss`

The issuer field is always `Upstash`.

##### `sub`

The url of your endpoint, where this request is sent to.

For example when you are using a nextjs app on vercel, this would look something
like `https://my-app.vercel.app/api/endpoint`

##### `exp`

A unix timestamp in seconds after which you should no longer accept this
request. Our JWTs have a lifetime of 5 minutes by default.

##### `iat`

A unix timestamp in seconds when this JWT was created.

##### `nbf`

A unix timestamp in seconds before which you should not accept this request.

##### `jti`

A unique id for this token.

##### `body`

The body field is a base64 encoded sha256 hash of the request body. We use url
encoding as specified in
[RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648#section-5).

#### Verifying the signature

See [how to verify the signature](/docs/qstash/howto/signature).

# URL Groups
Source: https://upstash.com/docs/qstash/features/url-groups

Sending messages to a single endpoint and not having to worry about retries is
already quite useful, but we also added the concept of URL Groups to QStash.

In short, a URL Group is just a namespace where you can publish messages to, the
same way as publishing a message to an endpoint directly.

After creating a URL Group, you can create one or multiple endpoints. An endpoint is
defined by a publicly available URL where the request will be sent to each
endpoint after it is published to the URL Group.

When you publish a message to a URL Group, it will be fanned out and sent to all the
subscribed endpoints.

## When should I use URL Groups?

URL Groups decouple your message producers from consumers by grouping one or more
endpoints into a single namespace.

Here's an example: You have a serverless function which is invoked with each
purchase in your e-commerce site. You want to send email to the customer after
the purchase. Inside the function, you submit the URL `api/sendEmail` to the
QStash. Later, if you want to send a Slack notification, you need to update the
serverless function adding another call to QStash to submit
`api/sendNotification`. In this example, you need to update and redeploy the
Serverless function at each time you change (or add) the endpoints.

If you create a URL Group `product-purchase` and produce messages to that URL Group in
the function, then you can add or remove endpoints by only updating the URL Group.
URL Groups give you freedom to modify endpoints without touching the backend
implementation.

Check [here](/docs/qstash/howto/publishing#publish-to-url-group) to learn how to publish
to URL Groups.

## How URL Groups work

When you publish a message to a URL Group, we will enqueue a unique task for each
subscribed endpoint and guarantee successful delivery to each one of them.

[![]()](https://mermaid.live/edit#pako:eNp1kl1rgzAUhv9KyOWoddXNtrkYVNdf0F0U5ijRHDVMjctHoRT_-2KtaztUQeS8j28e8JxxKhhggpWmGt45zSWtnKMX13GN7PX59IUc5w19iIanBDUmKbkq-qwfXuKdSVQqeQLssK1ZI3itVQ9dekdzdO6Ja9ntKKq-DxtEoP4xYGCIr-OOGCoOG4IYlPwIcqBu0V0XQRK0PE0w9lyCvP1-iB1n1CgcNwofjcJpo_Cua8ooHDWadIrGnaJHp2jaKbrrmnKK_jl1d9s98AxXICvKmd2fy8-MsS6gghgT-5oJCUrH2NKWNA2zi7BlXAuJSUZLBTNMjRa7U51ioqWBAbpu4R9VCsrAfnTG-tR0u5pzpW1lKuqM593cyNKOC60bRVy3i-c514VJ5qmoXMVZQaUujuvADbxgRT0fgqVPX32fpclivcq8l0XGls8Lj-K2bX8Bx2nzPg)

Consider this scenario: You have a URL Group and 3 endpoints that are subscribed to
it. Now when you publish a message to the URL Group, internally we will create a
task for each subscribed endpoint and handle all retry mechanism isolated from
each other.

## How to create a URL Group

Please refer to the howto [here](/docs/qstash/howto/url-group-endpoint).

# Debug Logs
Source: https://upstash.com/docs/qstash/howto/debug-logs

To debug the logs, first you need to understand the different states a message can
be in.

Older logs are removed automatically. See our [pricing page](https://upstash.com/pricing/qstash) for log retention limits.

## Lifecycle of a Message

To understand the lifecycle of each message, we'll look at the following chart:

[comment]: # (https://mermaid.live/edit#pako:eNptU9uO2jAQ_RXLjxVXhyTED5UQpBUSZdtAK7VNtfLGTmIpsZHjrEoR_17HBgLdztPMmXPm4ssJZpIyiGGjiWYrTgpF6uErSgUw9vPdLzAcvgfLJF7s45UDL4FNbEnN6FLWB9lwzVz-EbO0xXK__hb_L43Bevv8OXn6mMS7nSPYSf6tcgIXc5zOkniffH9TvrM4SZ4Sm3GcXne-rLDYLuPNcxJ_-Rrvrrs4cGMiRxLS9K1YroHM3yowqFnTkIKBjIiMVYA3xqsqRp3azWQLu3EwaFUFFNOtEg3ICa9uU91xV_HGuIltcM9v2iwz_fpN-u0_LNYbyzdcdQQVr7k2PsnK6yx90Y5vLtXBF-ED1h_CA5wKOICF4hRirVo2gDVTNelCeOoYKdQlq1kKsXEpy0lb6RSm4mxkByJ-SFlflUq2RQlxTqrGRO2B9u_uhpJWy91RZFeNY8WUa6lupEoSykx4gvp46J5wwRtt-mVS5LzocHOABi61PjR4PO7So4Lrsn0ZZbIeN5yWROnyNQrGAQrmBHksCD3iex7NXqbRPEezaU7DyRQReD4PILP9P7n_Yr-N2YYJM8RStkJDHHqRXbfr_RviaDbyQg9NJz7yg9ksCAfwCHGARn6AfC9CKJqiiT83lf_Y85mM5uEsurfzX7VrENs)
  <img />

Either you or a previously setup schedule will create a message.

When a message is ready for execution, it will be become `ACTIVE` and a delivery to
your API is attempted.

If you API responds with a status code between `200 - 299`, the task is
considered successful and will be marked as `DELIVERED`.

Otherwise the message is being retried if there are any retries left and moves to `RETRY`. If all retries are exhausted, the task has `FAILED` and the message will be moved to the DLQ.

During all this a message can be cancelled via [DELETE /v2/messages/:messageId](/docs/qstash/api-reference/messages/cancel-a-message). When the request is received, `CANCEL_REQUESTED` will be logged first.
If retries are not exhausted yet, in the next deliver time, the message will be marked as `CANCELLED` and will be completely removed from the system.

## Console

Head over to the [Upstash Console](https://console.upstash.com/qstash) and go to
the `Logs` tab, where you can see the latest status of your messages.

# Delete Schedules
Source: https://upstash.com/docs/qstash/howto/delete-schedule

Deleting schedules can be done using the [schedules api](/docs/qstash/api-reference/schedules/delete-a-schedule).

<CodeGroup>
```shell cURL
curl -XDELETE \
    -H 'Authorization: Bearer XXX' \
    'https://qstash.upstash.io/v2/schedules/<schedule_id>'
```

```typescript Typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.schedules.delete("<scheduleId>");
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.schedule.delete("<scheduleId>")
```

</CodeGroup>

Deleting a schedule does not stop existing messages from being delivered. It
only stops the schedule from creating new messages.

## Schedule ID

If you don't know the schedule ID, you can get a list of all of your schedules
from [here](/docs/qstash/api-reference/schedules/list-schedules).

<CodeGroup>
```shell cURL
curl \
    -H 'Authorization: Bearer XXX' \
    'https://qstash.upstash.io/v2/schedules'
```
```typescript Typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const allSchedules = await client.schedules.list();
```
```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.schedule.list()
```
</CodeGroup>

# Handling Failures
Source: https://upstash.com/docs/qstash/howto/handling-failures

Sometimes, endpoints fail due to various reasons such as network issues or server issues.
In such cases, QStash offers a few options to handle these failures.

## Failure Callbacks

When publishing a message, you can provide a failure callback that will be called if the message fails to be published.
You can read more about callbacks [here](/docs/qstash/features/callbacks).

With the failure callback, you can add custom logic such as logging the failure or sending an alert to the team.
Once you handle the failure, you can [delete it from the dead letter queue](/docs/qstash/api-reference/dlq/delete-a-dlq-message).

<CodeGroup>
```bash cURL
curl -X POST \
  https://qstash.upstash.io/v2/publish/<DESTINATION_URL> \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <QSTASH_TOKEN>' \
  -H 'Upstash-Failure-Callback: <CALLBACK_URL>' \
  -d '{ "hello": "world" }'
```

```typescript Typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const res = await client.publishJSON({
  url: "https://my-api...",
  body: { hello: "world" },
  failureCallback: "https://my-callback...",
});
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://my-api...",
    body={
        "hello": "world",
    },
    failure_callback="https://my-callback...",
)
```
</CodeGroup>

## Dead Letter Queue

If you don't want to handle the failure immediately, you can use the dead letter queue (DLQ) to store the failed messages.
You can read more about the dead letter queue [here](/docs/qstash/features/dlq).

Failed messages are automatically moved to the dead letter queue upon failure, and can be retried from the console or
via the [REST API](/docs/qstash/api-reference/dlq/retry-a-dlq-message).

  <img alt="DLQ from console" />

# Local Development
Source: https://upstash.com/docs/qstash/howto/local-development

QStash requires a publicly available API to send messages to.
During development when applications are not yet deployed, developers typically need to expose their local API by creating a public tunnel.

While local tunneling works seamlessly, it requires code changes between development and production environments and increases friction for developers.
To simplify the development process, Upstash provides QStash CLI, which allows you to run a development server locally for testing and development.

<Check>The development server fully supports all QStash features including Schedules, URL Groups, Workflows, and Event Logs.</Check>

<Note>Since the development server operates entirely in-memory, all data is reset when the server restarts.</Note>

## Automatic dev server (JavaScript SDK)

If you are using `@upstash/qstash`, you can just set `QSTASH_DEV=true` in your environment variables, and the SDK will download and connect to the dev server automatically.

```bash .env
QSTASH_DEV=true
```

Additionally, you can pass `devMode: true` to explicitly enable dev mode:

```typescript
import { Client } from "@upstash/qstash";

const client = new Client({ devMode: true });

await client.publishJSON({
  url: "https://example.com/webhook",
  body: { hello: "world" },
});
```

```typescript
// app/api/webhook/route.ts
import { verifySignatureAppRouter } from "@upstash/qstash/nextjs";

export const POST = verifySignatureAppRouter(
  async (request) => {
    // signed by the local dev server
    return new Response("ok");
  },
  { devMode: true }
);
```

On startup the SDK prints the dev server URL and a link to the Upstash Console:

```
[QStash Dev] Server running at http://127.0.0.1:8080
  Console: https://console.upstash.com/qstash/local-mode-user?port=8080
```

When dev mode is active, the SDK will:

* Download the latest `qstash` binary on first use, cached in your OS cache directory:
  * macOS: `~/Library/Caches/upstash/qstash-dev`
  * Linux: `~/.cache/upstash/qstash-dev`
  * Windows: `%LOCALAPPDATA%\upstash\qstash-dev`
* Spawn the server on port `8080` (override with `QSTASH_DEV_PORT`).
* Confirm the server is running on the configured port and skip spawning if it is.
* Override the `baseUrl`, `token`, and signing keys you provide and use the dev server's instead.

<Warning>
Dev mode is automatically a no-op when `NODE_ENV=production` and in browser/edge runtimes.
</Warning>

### Next.js edge routes

If you are using edge routes in Next.js, the SDK cannot spawn the dev server from the edge runtime — it can only verify it is reachable. To start the server before any edge request hits, call `registerQStashDev()` from `instrumentation.ts`:

```typescript
// instrumentation.ts
import { registerQStashDev } from "@upstash/qstash/nextjs";

export const register = () => registerQStashDev();
```

## Running the dev server manually

If you would rather run the dev server manually (Python, Go, plain HTTP, etc.), use the QStash CLI directly.

### @upstash/qstash-cli

Install the binary via the `@upstash/qstash-cli` NPM package:

```javascript
npx @upstash/qstash-cli dev

// Start on a different port
npx @upstash/qstash-cli dev -port=8081

// Start with a custom log server port
npx @upstash/qstash-cli dev -port=8081 -log-port=9000
```

Once you start the local server, you can go to the QStash tab on Upstash Console and enable local mode, which will allow you to publish requests and monitor messages with the local server.

<img />

### Docker

QStash CLI is available as a Docker image through our public AWS ECR repository:

```javascript
// Pull the image
docker pull public.ecr.aws/upstash/qstash:latest

// Run the image
docker run -p 8080:8080 public.ecr.aws/upstash/qstash:latest qstash dev
```

### Direct download

You can download the binary directly from our artifact repository without using a package manager:

https://artifacts.upstash.com/#qstash/versions/

Select the appropriate version, architecture, and operating system for your platform.
After extracting the archive file, run the executable:

```
$ ./qstash dev
```

## CLI reference

Currently, the only available command for QStash CLI is `dev`, which starts a development server instance.

```
$ ./qstash dev --help
Usage of dev:
  -log-port int
        Port to run the QStash log server on [env QSTASH_DEV_LOG_PORT]
  -port int
        Port to run the QStash server on [env QSTASH_DEV_PORT] (default 8080)
  -quota string
        The quota of users [env QSTASH_DEV_QUOTA] (default "payg")
```

### Server Components

Running `qstash dev` starts two components:

* **QStash server** — the main API server, defaults to port `8080`. Use `-port` or `QSTASH_DEV_PORT` to change it.
* **Log server** — serves logs and is used by the Upstash Console and Logs API. Defaults to `port + 1` (i.e., `8081` when using the default port). Use `-log-port` or `QSTASH_DEV_LOG_PORT` to change it.

```
# Start with custom ports for both components
$ ./qstash dev -port=8080 -log-port=9000
```

## Test users

There are predefined test users available. You can configure the quota type of users using the `-quota` option, with available options being `payg` and `pro`.
These quotas don't affect performance but allow you to simulate different server limits based on the subscription tier.

After starting the development server using any of the methods above, it will display the necessary environment variables.
Select and copy the credentials from one of the following test users:

<CodeGroup>

```javascript User 1
QSTASH_URL="http://localhost:8080"
QSTASH_TOKEN="eyJVc2VySUQiOiJkZWZhdWx0VXNlciIsIlBhc3N3b3JkIjoiZGVmYXVsdFBhc3N3b3JkIn0="
QSTASH_CURRENT_SIGNING_KEY="sig_7kYjw48mhY7kAjqNGcy6cr29RJ6r"
QSTASH_NEXT_SIGNING_KEY="sig_5ZB6DVzB1wjE8S6rZ7eenA8Pdnhs"
```

```javascript User 2
QSTASH_URL="http://localhost:8080"
QSTASH_TOKEN="eyJVc2VySUQiOiJ0ZXN0VXNlcjEiLCJQYXNzd29yZCI6InRlc3RQYXNzd29yZCJ9"
QSTASH_CURRENT_SIGNING_KEY="sig_7GVPjvuwsfqF65iC8fSrs1dfYruM"
QSTASH_NEXT_SIGNING_KEY="sig_5NoELc3EFnZn4DVS5bDs2Nk4b7Ua"
```

```javascript User 3
QSTASH_URL="http://localhost:8080"
QSTASH_TOKEN="eyJVc2VySUQiOiJ0ZXN0VXNlcjIiLCJQYXNzd29yZCI6InRlc3RQYXNzd29yZCJ9"
QSTASH_CURRENT_SIGNING_KEY="sig_6jWGaWRxHsw4vMSPJprXadyvrybF"
QSTASH_NEXT_SIGNING_KEY="sig_7qHbvhmahe5GwfePDiS5Lg3pi6Qx"
```

```javascript User 4
QSTASH_URL="http://localhost:8080"
QSTASH_TOKEN="eyJVc2VySUQiOiJ0ZXN0VXNlcjMiLCJQYXNzd29yZCI6InRlc3RQYXNzd29yZCJ9"
QSTASH_CURRENT_SIGNING_KEY="sig_5T8FcSsynBjn9mMLBsXhpacRovJf"
QSTASH_NEXT_SIGNING_KEY="sig_7GFR4YaDshFcqsxWRZpRB161jguD"
```

</CodeGroup>

## License

The QStash development server is licensed under the [Development Server License](/docs/qstash/misc/license), which restricts its use to development and testing purposes only.
It is not permitted to use it in production environments. Please refer to the full license text for details.

# Local Tunnel
Source: https://upstash.com/docs/qstash/howto/local-tunnel

QStash requires a publicly available API to send messages to.
The recommended approach is to run a [development server](/docs/qstash/howto/local-development) locally and use it for development purposes.

Alternatively, you can set up a local tunnel to expose your API, enabling QStash to send requests directly to your application during development.

## localtunnel.me

[localtunnel.me](https://github.com/localtunnel/localtunnel) is a free service to provide
a public endpoint for your local development.

It's as simple as running

```
npx localtunnel --port 3000
```
replacing `3000` with the port your application is running on.

This will give you a public URL like `https://good-months-leave.loca.lt` which can be used
as your QStash URL.

If you run into issues, you may need to set the `Upstash-Forward-bypass-tunnel-reminder` header to
any value to bypass the reminder message.

## ngrok

[ngrok](https://ngrok.com) is a free service, that provides you with a public
endpoint and forwards all traffic to your localhost.

### Sign up

Create a new account on
[dashboard.ngrok.com/signup](https://dashboard.ngrok.com/signup) and follow the
[instructions](https://dashboard.ngrok.com/get-started/setup) to download the
ngrok CLI and connect your account:

```bash
ngrok config add-authtoken XXX
```

### Start the tunnel

Choose the port where your application is running. Here I'm forwarding to port
3000, because Next.js is using it.

```bash
$ ngrok http 3000

Session Status                online
Account                       Andreas Thomas (Plan: Free)
Version                       3.1.0
Region                        Europe (eu)
Latency                       -
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://e02f-2a02-810d-af40-5284-b139-58cc-89df-b740.eu.ngrok.io -> http://localhost:3000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00
```

### Publish a message

Now copy the `Forwarding` url and use it as destination in QStash. Make sure to
add the path of your API at the end. (`/api/webhooks` in this case)

```
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://e02f-2a02-810d-af40-5284-b139-58cc-89df-b740.eu.ngrok.io/api/webhooks'
```

### Debug

In case messages are not delivered or something else doesn't work as expected,
you can go to [http://127.0.0.1:4040](http://127.0.0.1:4040) to see what ngrok
is doing.

# Select a Region
Source: https://upstash.com/docs/qstash/howto/multi-region

## Overview

QStash is available in two regions: **EU region** and **US region**. Each region is completely independent with its own infrastructure, pricing, resources, logs, and messages.

## Regional URLs

* **EU Region**: `https://qstash-eu-central-1.upstash.io`, or `https://qstash.upstash.io`
* **US Region**: `https://qstash-us-east-1.upstash.io`

`QSTASH_URL` is optional. When it is not set, SDKs default to the EU region
(`https://qstash.upstash.io`). To use the US region, set `QSTASH_URL` to the US
regional URL together with that region's `QSTASH_TOKEN` and signing keys — you can
copy all of them from the **Quickstart** section of the
[QStash Console](https://console.upstash.com/qstash).

## Key Concepts

Each QStash region maintains:
* Usage in each region is tracked and billed independently
* Messages, queues, schedules, URL groups and DLQ are region-specific
* Each region has its own API tokens and signing keys

### Migration Between Regions

If you don't have any active resources (active messages, schedules, url groups etc), you can simply update your environment variables with the new region to migrate. If you have active resources, you will need to migrate more gracefully, as described below.

You can migrate your QStash resources from one region to another using the Upstash Console:

1. Navigate to the [QStash tab on Upstash Console](https://console.upstash.com/qstash)
2. Click the **Migrate** button
3. Follow the guided migration process

<img />

The migration tool will:
* Help you set up migration mode environment variables
* Copy and update your QStash resources (schedules, url groups, queues)

Your message logs or DLQ aren't part of the migration. They will remain in the old region.

<Info>
After migration, your app will be able to handle requests from both regions simultaneously to ensure a smooth transition.
</Info>

## Operating Modes

QStash SDK supports two modes of operation:

### Single-Region Mode (Default)

When `QSTASH_REGION` environment variable is **not set**, the SDK operates in single-region mode:

* Uses `QSTASH_TOKEN` and `QSTASH_URL` (or defaults to EU region)
* All outgoing messages are sent through the configured region
* Incoming messages are verified using default signing keys if signing keys are defined

```bash
# Single-region configuration (EU)
QSTASH_URL="https://qstash.upstash.io"
QSTASH_TOKEN="your_eu_token"
QSTASH_CURRENT_SIGNING_KEY="your_eu_current_key"
QSTASH_NEXT_SIGNING_KEY="your_eu_next_key"
```

### Migration Mode

When `QSTASH_REGION` is set to `US_EAST_1` or `EU_CENTRAL_1`, the SDK enables migration mode:

* Uses region-specific credentials (e.g., `US_EAST_1_QSTASH_TOKEN`)
* Automatically handles region detection for incoming requests
* Supports receiving messages from multiple regions simultaneously

<Note>
  If a message was published in one region, it will still be delivered from that region after the migration.
</Note>

Environment variables:

```bash
# Migration mode configuration with US as primary
QSTASH_REGION="US_EAST_1"

US_EAST_1_QSTASH_URL="https://qstash-us-east-1.upstash.io"
US_EAST_1_QSTASH_TOKEN="your_us_token"
US_EAST_1_QSTASH_CURRENT_SIGNING_KEY="your_us_current_key"
US_EAST_1_QSTASH_NEXT_SIGNING_KEY="your_us_next_key"

EU_CENTRAL_1_QSTASH_URL="https://qstash-eu-central-1.upstash.io"
EU_CENTRAL_1_QSTASH_TOKEN="your_eu_token"
EU_CENTRAL_1_QSTASH_CURRENT_SIGNING_KEY="your_eu_current_key"
EU_CENTRAL_1_QSTASH_NEXT_SIGNING_KEY="your_eu_next_key"
```

<Warning>
  Migration mode relies on environment variables being available via `process.env`. It won't work on platforms where `process.env` is not available, such as Cloudflare Workers.
</Warning>

#### Verifying Incoming Requests

QStash includes an `upstash-region` header with every request to indicate the source region:

```
upstash-region: US-EAST-1
```

With this header, the SDK can determine which signing keys to use when verifying the request if `QSTASH_REGION` is set. For this to work correctly, value of `upstash-region` header should be passed to the `verify` method:

```typescript {10-11}
import { Receiver } from "@upstash/qstash";

// Initialize receiver (works in both modes)
const receiver = new Receiver();

// Verify the incoming request
await receiver.verify({
  signature: request.headers.get("upstash-signature")!,
  body: await request.text(),
  // Pass the region header for multi-region support
  upstashRegion: request.headers.get("upstash-region") ?? undefined,
});
```

#### Platform-Specific Verification

Most platform verifiers automatically handle the region header:

```typescript
// Next.js App Router - automatically handles multi-region
import { verifySignatureAppRouter } from "@upstash/qstash/nextjs";

export const POST = verifySignatureAppRouter(async (req) => {
  const body = await req.json();
  return Response.json({ success: true });
});
```

## SDK Requirements

Migration mode support requires:
* `@upstash/qstash` >= 2.9.0

Update your dependency:

```bash
npm install @upstash/qstash@latest
```

# Publish Messages
Source: https://upstash.com/docs/qstash/howto/publishing

Publishing a message is as easy as sending a HTTP request to the `/publish`
endpoint. All you need is a valid url of your destination.

  <img width="688" />

<Info>
Destination URLs must always include the protocol (`http://` or `https://`)
</Info>

## The message

The message you want to send is passed in the request body. Upstash does not
use, parse, or validate the body, so you can send any kind of data you want. We
suggest you add a `Content-Type` header to your request to make sure your
destination API knows what kind of data you are sending.

## Sending custom HTTP headers

In addition to sending the message itself, you can also forward HTTP headers.
Simply add them prefixed with `Upstash-Forward-` and we will include them in the
message.

#### Here's an example

<CodeGroup>
```shell cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H 'Upstash-Forward-My-Header: my-value' \
    -H "Content-type: application/json" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://example.com'
```
``` typescript Typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const res = await client.publishJSON({
  url: "https://example.com",
  body: { "hello": "world" },
  headers: { "my-header": "my-value" },
});
```
``` python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://my-api...",
    body={
        "hello": "world",
    },
    headers={
        "my-header": "my-value",
    },
)
```
</CodeGroup>

In this case, we would deliver a `POST` request to `https://example.com` with
the following body and headers:

```json
// body
{ "hello": "world" }

// headers
My-Header:      my-value
Content-Type:   application/json
```

#### What happens after publishing?

When you publish a message, it will be durably stored in an
[Upstash Redis database](https://upstash.com/redis). Then we try to deliver the
message to your chosen destination API. If your API is down or does not respond
with a success status code (200-299), the message will be retried and delivered
when it comes back online. You do not need to worry about retrying messages or
ensuring that they are delivered.

By default, the multiple messages published to QStash are sent to your API in parallel.

## Publish to URL Group

URL Groups allow you to publish a single message to more than one API endpoints. To
learn more about URL Groups, check [URL Groups section](/docs/qstash/features/url-groups).

Publishing to a URL Group is very similar to publishing to a single destination. All
you need to do is replace the `URL` in the `/publish` endpoint with the URL Group
name.

```
https://qstash.upstash.io/v2/publish/https://example.com
https://qstash.upstash.io/v2/publish/my-url-group
```

<CodeGroup>
```shell cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/my-url-group'
```
``` typescript Typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const res = await client.publishJSON({
  urlGroup: "my-url-group",
  body: { "hello": "world" },
});
```
``` python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url_group="my-url-group",
    body={
        "hello": "world",
    },
)
```
</CodeGroup>

## Optional parameters and configuration

QStash supports a number of optional parameters and configuration that you can
use to customize the delivery of your message. All configuration is done using
HTTP headers.

# Read-only Token
Source: https://upstash.com/docs/qstash/howto/readonly-token

You can use a read-only version of your token to safely share it with your team or AI agents.
This token has access to your logs, messages, schedules, and other resources, but it cannot be used to publish messages, create new resources such as schedules or URL Groups, or modify existing resources.

You can get your read-only token from the dashboard by clicking "Read-only token" in the Quickstart section:

  <img />

<Info>
Note that a read-only token can still access sensitive data such as logs and messages.

If you want to share your token without exposing sensitive data, you can use the ["Redact Fields"](/docs/qstash/howto/redact-fields) feature to hide sensitive data from the logs and resources.
</Info>

# Receiving Messages
Source: https://upstash.com/docs/qstash/howto/receiving

When you publish a message, QStash will deliver it to your chosen destination. This is a brief overview of how a request to your API looks like.

## Headers

We are forwarding all headers that have been prefixed with `Upstash-Forward-` to your API. [Learn more](/docs/qstash/howto/publishing#sending-custom-http-headers)

In addition to your custom headers, we're sending these headers as well:

| Header                | Description                                                          |
| ----------------------| -------------------------------------------------------------------- |
| `User-Agent`          | Will be set to `Upstash-QStash`                                      |
| `Content-Type`        | The original `Content-Type` header                                   |
| `Upstash-Topic-Name`  | The URL Group (topic) name if sent to a URL Group                    |
| `Upstash-Signature`   | The signature you need to verify [See here](/docs/qstash/howto/signature) |
| `Upstash-Retried`     | How often the message has been retried so far. Starts with 0.        |
| `Upstash-Message-Id`  | The message id of the message.                                       |
| `Upstash-Schedule-Id` | The schedule id of the message if it is related to a schedule.       |
| `Upstash-Caller-Ip`   | The IP address of the publisher of this message.                     |

## Body

The body is passed as is, we do not modify it at all. If you send a JSON body, you will receive a JSON body. If you send a string, you will receive a string.

## Verifying the signature

[See here](/docs/qstash/howto/signature)

# Redact Private Data
Source: https://upstash.com/docs/qstash/howto/redact-fields

QStash messages can contain private data that you don't want visible in the Upstash Console, or API responses.

QStash allows you to redact specific fields so they appear as `REDACTED:<SHA256>` in the dashboard and API. The original values are still used when delivering messages to your endpoint. The SHA256 hash lets you verify the two data without revealing the original data.

To redact a field, pass the `redact` option when publishing a message.

Available options:
| Option | Description |
|--|--|
| body | Redact the body of the message |
| headers | Redact the headers of the message |
| headers[header_name] | Redact a specific header (e.g., `headers[Authorization]`) |

<Info>
Redaction is one-way. Once a field is redacted, the original value cannot be retrieved from the API or dashboard.
</Info>

<CodeGroup>
```typescript TypeScript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });

const res = await client.publishJSON({
  url: "https://my-api...",
  body: { hello: "world" },
  redact: {
    body: true,
    header: ["Authorization"] // or `header: true` to redact all headers
  },
});
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://my-api...",
    body={
        "hello": "world",
    },
    redact={
      "body": True,
      "header": ["Authorization"] // or `header: True` to redact all headers
    },
)
```

```bash cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-Type: application/json" \
    -H "Upstash-Redact-Fields: body, header[Authorization]" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://my-api...'
```
</CodeGroup>

  <img />

Redaction is configured per message, so you can redact different fields for different messages.

If a message fails and moves to the DLQ, the redacted fields remain redacted in the DLQ.
However, when you retry a DLQ message, QStash delivers the original values to your endpoint.

## Schedules

You can also redact fields in schedules. Pass the `redact` option when creating a schedule, and all messages produced by that schedule will have the specified fields redacted.
Schedule get and list endpoints also apply redaction, so private data won't appear in API responses or the dashboard.

<CodeGroup>
```typescript TypeScript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const res = await client.schedule.create({
  name: "my-schedule",
  cron: "0 * * * *",
  url: "https://my-api...",
  body: { hello: "world" },
  redact: {
    body: true,
    header: ["Authorization"]
  },
});
```

```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.schedule.create(
    name="my-schedule",
    cron="0 * * * *",
    url="https://my-api...",
    body={"hello": "world"},
    redact={
      "body": True,
      "header": ["Authorization"]
    },
)
```

```shell cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -H "Upstash-Cron: * * * * *" \
    -H "Upstash-Redact-Fields: body, header[Authorization]" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/schedules/https://example.com'
```
</CodeGroup>

  <img />

<Warning>
When updating a redacted schedule via the dashboard or API, you must provide the original values for the redacted fields.

If you don't provide the original values, the redacted fields will be saved as `REDACTED:<SHA256>` — which is the hash value visible in the dashboard, not the original data.
</Warning>

# Reset Token
Source: https://upstash.com/docs/qstash/howto/reset-token

Your token is used to interact with the QStash API. You need it to publish
messages as well as create, read, update or delete other resources, such as
URL Groups and endpoints.

Resetting your token will invalidate your current token and all future requests
with the old token will be rejected.

To reset your token, simply click on the "Reset token" button at the bottom in
the [QStash UI](https://console.upstash.com/qstash) and confirm the dialog.

![]()

Afterwards you should immediately update your token in all your applications.

# Roll Your Signing Keys
Source: https://upstash.com/docs/qstash/howto/roll-signing-keys

Because your API needs to be publicly accessible from the internet, you should
make sure to verify the authenticity of each request.

Upstash provides a JWT with each request. This JWT is signed by your individual
secret signing keys. [Read more](/docs/qstash/howto/signature).

We are using 2 signing keys:

* current: This is the key used to sign the JWT.
* next: This key will be used to sign after you have rolled your keys.

If we were using only a single key, there would be some time between when you
rolled your keys and when you can edit the key in your applications. In order to
minimize downtime, we use 2 keys and you should always try to verify with both
keys.

## What happens when I roll my keys?

When you roll your keys, the current key will be replaced with the next key and
a new next key will be generated.

```
currentKey = nextKey
nextKey = generateNewKey()
```

<Warning>

Rolling your keys twice without updating your applications will cause your apps
to reject all requests, because both the current and next keys will have been
replaced.

</Warning>

## How to roll your keys

Rolling your keys can be done by going to the
[QStash UI](https://console.upstash.com/qstash) and clicking on the "Roll keys"
button.

![]()

# Verify Signatures
Source: https://upstash.com/docs/qstash/howto/signature

We send a JWT with each request. This JWT is signed by your individual secret
signing key and sent in the `Upstash-Signature` HTTP header.

You can use this signature to verify the request is coming from QStash.

![]()

<Warning>
You need to keep your signing keys in a secure location.
Otherwise some malicious actor could use them to send requests to your API as if they were coming from QStash.
</Warning>

## Verifying

You can use the official QStash SDKs or implement a custom verifier either by using [an open source library](https://jwt.io/libraries) or by processing the JWT manually.

### Via SDK (Recommended)

QStash SDKs provide a `Receiver` type that simplifies signature verification.

<CodeGroup>
```typescript Typescript
import { Receiver } from "@upstash/qstash";

const receiver = new Receiver({
  currentSigningKey: "YOUR_CURRENT_SIGNING_KEY",
  nextSigningKey: "YOUR_NEXT_SIGNING_KEY",
});

// ... in your request handler

const signature = req.headers["Upstash-Signature"];
const body = req.body;

const isValid = await receiver.verify({
  body,
  signature,
  url: "YOUR-SITE-URL",
});
```

```python Python
from qstash import Receiver

receiver = Receiver(
    current_signing_key="YOUR_CURRENT_SIGNING_KEY",
    next_signing_key="YOUR_NEXT_SIGNING_KEY",
)

# ... in your request handler

signature, body = req.headers["Upstash-Signature"], req.body

receiver.verify(
    body=body,
    signature=signature,
    url="YOUR-SITE-URL",
)
```

```go Golang
import "github.com/qstash/qstash-go"

receiver := qstash.NewReceiver("<CURRENT_SIGNING_KEY>", "NEXT_SIGNING_KEY")

// ... in your request handler

signature := req.Header.Get("Upstash-Signature")
body, err := io.ReadAll(req.Body)
// handle err

err := receiver.Verify(qstash.VerifyOptions{
    Signature: signature,
    Body:      string(body),
    Url:       "YOUR-SITE-URL", // optional
})
// handle err
```
</CodeGroup>

<Warning>Depending on the environment, the body might be parsed into an object by the HTTP handler if it is JSON.
Ensure you use the raw body string as is. For example, converting the parsed object back to a string (e.g., JSON.stringify(object)) may cause inconsistencies and result in verification failure.</Warning>

### Manual verification

If you don't want to use the SDKs, you can implement your own verifier either by using an open-source library or by manually processing the JWT.

The exact implementation depends on the language of your choice and the library if you use one.
Instead here are the steps you need to follow:

1. Split the JWT into its header, payload and signature
2. Verify the signature
3. Decode the payload and verify the claims
   * `iss`: The issuer must be`Upstash`.
   * `sub`: The subject must the url of your API.
   * `exp`: Verify the token has not expired yet.
   * `nbf`: Verify the token is already valid.
   * `body`: Hash the raw request body using `SHA-256` and compare it with the
     `body` claim.

You can also reference the implementation in our
[Typescript SDK](https://github.com/upstash/sdk-qstash-ts/blob/main/src/receiver.ts#L82).

After you have verified the signature and the claims, you can be sure the
request came from Upstash and process it accordingly.

## Claims

All claims in the JWT are listed [here](/docs/qstash/features/security#claims)

# Create URL Groups and Endpoints
Source: https://upstash.com/docs/qstash/howto/url-group-endpoint

QStash allows you to group multiple APIs together into a single namespace,
called a `URL Group` (Previously, it was called `Topics`).
Read more about URL Groups [here](/docs/qstash/features/url-groups).

There are two ways to create endpoints and URL Groups: The UI and the REST API.

## UI

Go to [console.upstash.com/qstash](https://console.upstash.com/qstash) and click
on the `URL Groups` tab. Afterwards you can create a new URL Group by giving it a name.
Keep in mind that URL Group names are restricted to alphanumeric, underscore, hyphen
and dot characters.

![]()

After creating the URL Group, you can add endpoints to it:

![]()

## API

You can create a URL Group and endpoint using the [console](https://console.upstash.com/qstash) or [REST API](/docs/qstash/api-reference/url-groups/upsert-url-group-and-endpoint).

<CodeGroup>
```bash cURL
curl -XPOST https://qstash.upstash.io/v2/topics/:urlGroupName/endpoints \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "endpoints": [
      {
        "name": "endpoint1",
        "url": "https://example.com"
      },
      {
        "name": "endpoint2",
        "url": "https://somewhere-else.com"
      }
    ]
  }'
```
```typescript Typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const urlGroups = client.urlGroups;
await urlGroups.addEndpoints({
  name: "urlGroupName",
  endpoints: [
    { name: "endpoint1", url: "https://example.com" },
    { name: "endpoint2", url: "https://somewhere-else.com" },
  ],
});
```
```python Python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.url_group.upsert_endpoints(
    url_group="url-group-name",
    endpoints=[
        {"name": "endpoint1", "url": "https://example.com"},
        {"name": "endpoint2", "url": "https://somewhere-else.com"},
    ],
)
```
</CodeGroup>

# Use as Webhook Receiver
Source: https://upstash.com/docs/qstash/howto/webhook

You can configure QStash to receive and process your webhook calls.

Instead of having the webhook service call your endpoint directly, QStash acts as an intermediary, receiving the request and forwarding it to your endpoint.
QStash provides additional control over webhook requests, allowing you to configure properties such as delay, retries, timeouts, callbacks, and flow control.

There are multiple ways to configure QStash to receive webhook requests.

## 1. Publish

You can configure your webhook URL as a QStash publish request.

For example, if your webhook endpoint is:

`
https://example.com/api/webhook
`

Instead of using this URL directly as the webhook address, use:

`
https://qstash.upstash.io/v2/publish/https://example.com/api/webhook?qstash_token=<QSTASH_TOKEN>
`

<Note>
Request configurations such as custom retries, timeouts, and other settings can be specified using HTTP headers in the publish request.
Refer to the [REST API documentation](/docs/qstash/api-reference/messages/publish-a-message) for a full list of available configuration headers.

It’s also possible to pass configuration via query parameters. You can use the lowercase format of headers as the key, such as ?upstash-retries=3&upstash-delay=100s. This makes it easier to configure webhook messages.
</Note>

<Tip>
By default, any headers in the publish request that are prefixed with `Upstash-Forward-` will be forwarded to your endpoint.

However, since most webhook services do not allow header prefixing, we introduced a configuration option to enable forwarding all incoming request headers.

To enable this, set `Upstash-Header-Forward: true` in the publish request or append the query parameter `?upstash-header-forward=true` to the request URL. This ensures that all headers are forwarded to your endpoint without requiring the `Upstash-Forward-` prefix.
</Tip>

## 2. URL Group

URL Groups allow you to define server-side templates for publishing messages. You can create a URL Group either through the UI or programmatically.

For example, if your webhook endpoint is:

`https://example.com/api/webhook`

Instead of using this URL directly, you can create a URL Group and add this URL as an endpoint.

`
https://qstash.upstash.io/v2/publish/<URL_GROUP_NAME>?qstash_token=<QSTASH_TOKEN>
`

You can define default headers for a URL Group, which will automatically apply to all requests sent to that group.

```
curl -X PATCH https://qstash.upstash.io/v2/topics/<URL_GROUP_NAME> \
    -H "Authorization: Bearer <QSTASH_TOKEN>"
    -d '{
        "headers": {
            "Upstash-Header-Forward": ["true"],
            "Upstash-Retries": "3"
        }
    }'
```

When you save this header for your URL Group, it ensures that all headers are forwarded as needed for your webhook processing.

A URL Group also enables you to define multiple endpoints within group.
When a publish request is made to a URL Group, all associated endpoints will be triggered, allowing you to fan-out a single webhook call to multiple destinations.

# LLM with Anthropic
Source: https://upstash.com/docs/qstash/integrations/anthropic

QStash integrates smoothly with Anthropic's API, allowing you to send LLM requests and leverage QStash features like retries, callbacks, and batching. This is especially useful when working in serverless environments where LLM response times vary and traditional timeouts may be limiting. QStash provides an HTTP timeout of up to 2 hours, which is ideal for most LLM cases.

### Example: Publishing and Enqueueing Requests

Specify the `api` as `llm` with the provider set to `anthropic()` when publishing requests. Use the `Upstash-Callback` header to handle responses asynchronously, as streaming completions aren’t supported for this integration.

#### Publishing a Request

```typescript
import { anthropic, Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.publishJSON({
  api: { name: "llm", provider: anthropic({ token: "<ANTHROPIC_TOKEN>" }) },
  body: {
    model: "claude-3-5-sonnet-20241022",
    messages: [{ role: "user", content: "Summarize recent tech trends." }],
  },
  callback: "https://example.com/callback",
});
```

### Enqueueing a Chat Completion Request

Use `enqueueJSON` with Anthropic as the provider to enqueue requests for asynchronous processing.

```typescript
import { anthropic, Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });

const result = await client.queue({ queueName: "your-queue-name" }).enqueueJSON({
  api: { name: "llm", provider: anthropic({ token: "<ANTHROPIC_TOKEN>" }) },
  body: {
    model: "claude-3-5-sonnet-20241022",
    messages: [
      {
        role: "user",
        content: "Generate ideas for a marketing campaign.",
      },
    ],
  },
  callback: "https://example.com/callback",
});

console.log(result);
```

### Sending Chat Completion Requests in Batches

Use `batchJSON` to send multiple requests at once. Each request in the batch specifies the same Anthropic provider and includes a callback URL.

```typescript
import { anthropic, Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });

const result = await client.batchJSON([
  {
    api: { name: "llm", provider: anthropic({ token: "<ANTHROPIC_TOKEN>" }) },
    body: {
      model: "claude-3-5-sonnet-20241022",
      messages: [
        {
          role: "user",
          content: "Describe the latest in AI research.",
        },
      ],
    },
    callback: "https://example.com/callback1",
  },
  {
    api: { name: "llm", provider: anthropic({ token: "<ANTHROPIC_TOKEN>" }) },
    body: {
      model: "claude-3-5-sonnet-20241022",
      messages: [
        {
          role: "user",
          content: "Outline the future of remote work.",
        },
      ],
    },
    callback: "https://example.com/callback2",
  },
  // Add more requests as needed
]);

console.log(result);
```

#### Analytics with Helicone

To monitor usage, include Helicone analytics by passing your Helicone API key under `analytics`:

```typescript
await client.publishJSON({
  api: {
    name: "llm",
    provider: anthropic({ token: "<ANTHROPIC_TOKEN>" }),
    analytics: { name: "helicone", token: process.env.HELICONE_API_KEY! },
  },
  body: { model: "claude-3-5-sonnet-20241022", messages: [{ role: "user", content: "Hello!" }] },
  callback: "https://example.com/callback",
});
```

With this setup, Anthropic can be used seamlessly in any LLM workflows in QStash.

# Datadog - Upstash QStash Integration
Source: https://upstash.com/docs/qstash/integrations/datadog

This guide walks you through connecting your Datadog account with Upstash QStash for monitoring and analytics of your message delivery, retries, DLQ, and schedules.

<Check >
**Integration Scope**

Upstash Datadog Integration covers Prod Pack.

</Check>

## **Step 1: Log in to Your Datadog Account**

1. Go to [Datadog](https://www.datadoghq.com/) and sign in.

## **Step 2: Install Upstash Application**

1. In Datadog, open the Integrations page.
2. Search for "Upstash" and open the integration.

![integration-tab.png]()

Click "Install" to add Upstash to your Datadog account.

![installation.png]()

## **Step 3: Connect Accounts**

After installing Upstash, click "Connect Accounts". Datadog will redirect you to Upstash to complete account linking.

![connect-acc.png]()

## **Step 4: Select Account to Integrate**

1. On Upstash, select the Datadog account to integrate.
2. Personal and team accounts are supported.

**Caveats**

* The integration can be established once at a time. To change the account scope (e.g., add/remove teams), re-establish the integration from scratch.

![personal.png]()

![team.png]()

## **Step 5: Wait for Metrics Availability**

Once the integration is completed, metrics from QStash (publish counts, success/error rates, retries, DLQ, schedule executions) will start appearing in Datadog dashboards shortly.

![upstash-dashboard.png]()

## **Step 6: Datadog Integration Removal Process**

From Datadog → Integrations → Upstash, press "Remove" to break the connection.

### Confirm Removal

Upstash will stop publishing metrics after removal. Ensure any Datadog API keys/configurations for this integration are also removed on the Datadog side.

## **Conclusion**

You’ve connected Datadog with Upstash QStash. Explore Datadog dashboards to monitor message delivery performance and reliability.

If you need help, contact support.

# LLM - OpenAI
Source: https://upstash.com/docs/qstash/integrations/llm

QStash has built-in support for calling LLM APIs. This allows you to take advantage of QStash features such as retries, callbacks, and batching while using LLM APIs.

QStash is especially useful for LLM processing because LLM response times are often highly variable. When accessing LLM APIs from serverless runtimes, invocation timeouts are a common issue. QStash offers an HTTP timeout of 2 hours, which is sufficient for most LLM use cases. By using callbacks and the workflows, you can easily manage the asynchronous nature of LLM APIs.

## QStash LLM API

You can publish (or enqueue) single LLM request or batch LLM requests using all existing QStash features natively. To do this, specify the destination `api` as `llm` with a valid provider. The body of the published or enqueued message should contain a valid chat completion request. For these integrations, you must specify the `Upstash-Callback` header so that you can process the response asynchronously. Note that streaming chat completions cannot be used with them. Use [the chat API](#chat-api) for streaming completions.

All the examples below can be used with **OpenAI-compatible LLM providers**.

### Publishing a Chat Completion Request

<CodeGroup>
```js JavaScript
import { Client, upstash } from "@upstash/qstash";

const client = new Client({
    token: "<QSTASH_TOKEN>",
});

const result = await client.publishJSON({
    api: { name: "llm", provider: openai({ token: "_OPEN_AI_TOKEN_"}) },
    body: {
        model: "gpt-3.5-turbo",
        messages: [
            {
            role: "user",
            content: "Write a hello world program in Rust.",
            },
        ],
    },
    callback: "https://abc.requestcatcher.com/",
});

console.log(result);
```

```python Python
from qstash import QStash
from qstash.chat import upstash

q = QStash("<QSTASH_TOKEN>")

result = q.message.publish_json(
    api={"name": "llm", "provider": openai("<OPENAI_API_KEY>")},
    body={
        "model": "gpt-3.5-turbo",
        "messages": [
            {
                "role": "user",
                "content": "Write a hello world program in Rust.",
            }
        ],
    },
    callback="https://abc.requestcatcher.com/",
)

print(result)
```
</CodeGroup>

### Enqueueing a Chat Completion Request

<CodeGroup>
```js JavaScript
import { Client, upstash } from "@upstash/qstash";

const client = new Client({
    token: "<QSTASH_TOKEN>",
});

const result = await client.queue({ queueName: "queue-name" }).enqueueJSON({
    api: { name: "llm", provider: openai({ token: "_OPEN_AI_TOKEN_"}) },
    body: {
        "model": "gpt-3.5-turbo",
        messages: [
            {
                role: "user",
                content: "Write a hello world program in Rust.",
            },
        ],
    },
    callback: "https://abc.requestcatcher.com",
});

console.log(result);
```

```python Python
from qstash import QStash
from qstash.chat import upstash

q = QStash("<QSTASH_TOKEN>")

result = q.message.enqueue_json(
    queue="queue-name",
    api={"name": "llm", "provider": openai("<OPENAI_API_KEY>")},
    body={
        "model": "gpt-3.5-turbo",
        "messages": [
            {
                "role": "user",
                "content": "Write a hello world program in Rust.",
            }
        ],
    },
    callback="https://abc.requestcatcher.com",
)

print(result)
```

</CodeGroup>

### Sending Chat Completion Requests in Batches

<CodeGroup>
```js JavaScript
import { Client, upstash } from "@upstash/qstash";

const client = new Client({
    token: "<QSTASH_TOKEN>",
});

const result = await client.batchJSON([
    {
        api: { name: "llm", provider: openai({ token: "_OPEN_AI_TOKEN_" }) },
        body: { ... },
        callback: "https://abc.requestcatcher.com",
    },
    ...
]);

console.log(result);
```

```python Python
from qstash import QStash
from qstash.chat import upstash

q = QStash("<QSTASH_TOKEN>")

result = q.message.batch_json(
    [
        {
            "api":{"name": "llm", "provider": openai("<OPENAI_API_KEY>")},
            "body": {...},
            "callback": "https://abc.requestcatcher.com",
        },
        ...
    ]
)

print(result)
```

```shell curl
curl "https://qstash.upstash.io/v2/batch" \
    -X POST \
    -H "Authorization: Bearer QSTASH_TOKEN" \
    -H "Content-Type: application/json" \
    -d '[
        {
            "destination": "api/llm",
            "body": {...},
            "callback": "https://abc.requestcatcher.com"
        },
        ...
    ]'
```
</CodeGroup>

### Retrying After Rate Limit Resets

When the rate limits are exceeded, QStash automatically schedules the retry of
publish or enqueue of chat completion tasks depending on the reset time
of the rate limits. That helps with not doing retries prematurely
when it is definitely going to fail due to exceeding rate limits.

## Analytics via Helicone

Helicone is a powerful observability platform that provides valuable insights into your LLM usage. Integrating Helicone with QStash is straightforward.

To enable Helicone observability in QStash, you simply need to pass your Helicone API key when initializing your model. Here's how to do it for both custom models and OpenAI:

```ts
import { Client, custom } from "@upstash/qstash";

const client = new Client({
  token: "<QSTASH_TOKEN>",
});

await client.publishJSON({
  api: {
    name: "llm",
    provider: custom({
      token: "XXX",
      baseUrl: "https://api.together.xyz",
    }),
    analytics: { name: "helicone", token: process.env.HELICONE_API_KEY! },
  },
  body: {
    model: "meta-llama/Llama-3-8b-chat-hf",
    messages: [
      {
        role: "user",
        content: "hello",
      },
    ],
  },
  callback: "https://oz.requestcatcher.com/",
});
```

# n8n with QStash
Source: https://upstash.com/docs/qstash/integrations/n8n

Leverage your n8n workflow with Upstash Qstash, here is how you can make those requests using HTTP Request node.

### Step 1: Set Up an n8n Project

1. Go to https://n8n.io and create a new project
2. Create a Trigger as Webhook with default settings, this will be our entry point.
3. Create a HTTP Request Node
   <img />

***

### Step 2: Import QStash Configurations to HTTP Node

1. Go to Upstash Console and open QStash Request Builder Tab.
2. Fill out the fields to create an QStash Request. (Publish, Enqueue, Schedule)
   <img />
3. Copy the cURL snippet created for you, representing your request.
   <img />
4. Back to the n8n, in HTTP Request Parameters tab, use import cURL.
   <img />
5. Paste the cURL snippet that you copied in the console, and let n8n to fill out the form for you.
   <img />

***

### Step 3: Test the Workflow

1. Execute workflow.
2. Visit the Webhook URL.
3. That's it! You can check the logs in the Qstash Console to confirm your QStash Request is working.
   <img />

# Pipedream
Source: https://upstash.com/docs/qstash/integrations/pipedream

[Pipedream](https://pipedream.com) allows you to build and run workflows with
1000s of open source triggers and actions across 900+ apps.

Check out the [official integration](https://pipedream.com/apps/qstash).

## Trigger a Pipedream workflow from a QStash topic message

This is a step by step guide on how to trigger a Pipedream workflow from a
QStash topic message.

Alternatively [click here](https://pipedream.com/new?h=tch_3egfAX) to create a
new workflow with this QStash topic trigger added.

### 1. Create a Topic in QStash

If you haven't yet already, create a **Topic** in the
[QStash dashboard](https://console.upstash.com/qstash?tab=topics).

### 2. Create a new Pipedream workflow

Sign into [Pipedream](https://pipedream.com) and create a new workflow.

### 3. Add QStash Topic Message as a trigger

In the workflow **Trigger** search for QStash and select the **Create Topic
Endpoint** trigger.

![Select the QStash Create Topic Endpoint trigger]()

Then, connect your QStash account by clicking the QStash prop and retrieving
your token from the
[QStash dashboard](https://console.upstash.com/qstash?tab=details).

After connecting your QStash account, click the **Topic** prop, a dropdown will
appear containing the QStash topics on your account.

Then _click_ on a specific topic to listen for new messages on.

![Selecting a QStash topic to subscribe to]()

Finally, _click_ **Continue**. Pipedream will create a unique HTTP endpoint and
add it to your QStash topic.

### 4. Test with a sample message

Use the _Request Builder_ in the
[QStash dashboard](https://console.upstash.com/qstash?tab=details) to publish a
test message to your topic.

Alternatively, you can use the **Create topic message** action in a Pipedream
workflow to send a message to your topic.

_Don't forget_ to use this action in a separate workflow, otherwise you might
cause an infinite loop of messages between QStash and Pipedream.

### 5. Add additional steps

Add additional steps to the workflow by clicking the plus icon beneath the
Trigger step.

Build a workflow with the 1,000+ pre-built components available in Pipedream,
including [Airtable](https://pipedream.com/apps/airtable),
[Google Sheets](https://pipedream.com/apps/google-sheets),
[Slack](https://pipedream.com/apps/slack) and many more.

Alternatively, use [Node.js](https://pipedream.com/docs/code/nodejs) or
[Python](https://pipedream.com/docs/code/python) code steps to retrieve,
transform, or send data to other services.

### 6. Deploy your Pipedream workflow

After you're satisfied with your changes, click the **Deploy** button in the
top right of your Pipedream workflow. Your deployed workflow will not
automatically process new messages to your QStash topic. Collapse
quickstart-trigger-pipedream-workflow-from-topic.md 3 KB

### Video tutorial

If you prefer video, you can check out this tutorial by
[pipedream](https://pipedream.com).

[![Video]()](https://www.youtube.com/watch?v=-oXlWuxNG5A)

## Trigger a Pipedream workflow from a QStash topic message

This is a step by step guide on how to trigger a Pipedream workflow from a
QStash endpoint message.

Alternatively [click here](https://pipedream.com/new?h=tch_m5ofX6) to create a
pre-configured workflow with the HTTP trigger and QStash webhook verification
step already added.

### 1. Create a new Pipedream workflow

Sign into [Pipedream](https://pipedream.com) and create a new workflow.

### 2. Configure the workflow with an HTTP trigger

In the workflow **Trigger** select the **New HTTP / Webhook Requests** option.

![Create new HTTP Webhook trigger]()

Pipedream will create a unique HTTP endpoint for your workflow.

Then configure the HTTP trigger to _return a custom response_. By default
Pipedream will always return a 200 response, which allows us to return a non-200
response to QStash to retry the workflow again if there's an error during the
execution of the QStash message.

![Configure the webhook to return a custom response]()

Lastly, set the **Event Body** to be a **Raw request**. This will make sure the
QStash verify webhook action receives the data in the correct format.

![Set the event body to a raw body]()

### 3. Test with a sample message

Use the _Request Builder_ in the
[QStash dashboard](https://console.upstash.com/qstash?tab=details) to publish a
test message to your topic.

Alternatively, you can use the **Create topic message** action in a Pipedream
workflow to send a message to your topic.

_Don't forget_ to use this action in a separate workflow, otherwise you might
cause an infinite loop of messages between QStash and Pipedream.

### 4. Verify the QStash webhook

Pipedream has a pre-built QStash action that will verify the content of incoming
webhooks from QStash.

First, search for **QStash** in the step search bar, then select the QStash app.

Of the available actions, select the **Verify Webhook** action.

Then connect your QStash account and select the **HTTP request** prop. In the
dropdown, click **Enter custom expression** and then paste in
`{{ steps.trigger.event }}`.

This step will automatically verify the incoming HTTP requests and exit the
workflow early if requests are not from QStash.

### 5. Add additional steps

Add additional steps to the workflow by clicking the plus icon beneath the
Trigger step.

Build a workflow with the 1,000+ pre-built components available in Pipedream,
including [Airtable](https://pipedream.com/apps/airtable),
[Google Sheets](https://pipedream.com/apps/google-sheets),
[Slack](https://pipedream.com/apps/slack) and many more.

Alternatively, use [Node.js](https://pipedream.com/docs/code/nodejs) or
[Python](https://pipedream.com/docs/code/python) code steps to retrieve,
transform, or send data to other services.

### 6. Return a 200 response

In the final step of your workflow, return a 200 response by adding a new step
and selecting **Return an HTTP Response**.

![Returning an HTTP response]()

This will generate Node.js code to return an HTTP response to QStash using the
`$.respond` helper in Pipedream.

### 7. Deploy your Pipedream workflow

After you're satisfied with your changes, click the **Deploy** button in the
top right of your Pipedream workflow. Your deployed workflow will not
automatically process new messages to your QStash topic.

### Video tutorial

If you prefer video, you can check out this tutorial by
[pipedream](https://pipedream.com).

[![Video]()](https://youtu.be/uG8eO7BNok4)

# Prometheus - Upstash QStash Integration
Source: https://upstash.com/docs/qstash/integrations/prometheus

To monitor your QStash metrics in Prometheus and visualize in Grafana, follow these steps:

<Check >
**Integration Scope**

Upstash Prometheus Integration covers Prod Pack.

</Check>

## **Step 1: Enable Prometheus in Upstash Console**

1. Open the Upstash Console and navigate to QStash.
2. Go to Settings → Monitoring.
3. Enable Prometheus to allow scraping QStash metrics.

![configuration.png]()

## **Step 2: Copy Monitoring Token**

1. After enabling, a monitoring token is generated and displayed.
2. Copy the token. It will be used to authenticate Prometheus requests.

<Check >
**Header Format**

Send the token as `Authorization: Bearer <MONITORING_TOKEN>`.

</Check>

![monitoring-token.png]()

## **Step 3: Configure Prometheus (via Grafana Data Source)**

1. In Grafana, add a Prometheus data source.
2. Set the address to `https://api.upstash.com/monitoring/prometheus`.
3. In HTTP headers, add the monitoring token.

![datasource.png]()

![headers.png]()

Click <b>Test and Save</b>.

![datasource-final.png]()

## **Step 4: Import Dashboard**

You can use the Upstash Grafana dashboard to visualize QStash metrics.

Open the import dialog and use: <a href="https://grafana.com/grafana/dashboards/24206-upstash-qstash-dashboard/">Upstash QStash Dashboard</a>

![grafana-dashboard.png]()

## **Conclusion**

You’ve integrated QStash with Prometheus. Use Grafana to explore message throughput, retries, DLQ, schedules, and Upstash Workflows.

If you encounter issues, contact support.

# Email - Resend
Source: https://upstash.com/docs/qstash/integrations/resend

The `qstash-js` SDK offers an integration to easily send emails using [Resend](https://resend.com/), streamlining email delivery in your applications.

## Basic Email Sending

To send a single email, use the `publishJSON` method with the `resend` provider. Ensure your `QSTASH_TOKEN` and `RESEND_TOKEN` are set for authentication.

```typescript
import { Client, resend } from "@upstash/qstash";
const client = new Client({ token: "<QSTASH_TOKEN>" });

await client.publishJSON({
  api: {
    name: "email",
    provider: resend({ token: "<RESEND_TOKEN>" }),
  },
  body: {
    from: "Acme <onboarding@resend.dev>",
    to: ["delivered@resend.dev"],
    subject: "Hello World",
    html: "<p>It works!</p>",
  },
});
```

In the `body` field, specify any parameters supported by [the Resend Send Email API](https://resend.com/docs/api-reference/emails/send-email), such as `from`, `to`, `subject`, and `html`.

## Sending Batch Emails

To send multiple emails at once, use Resend’s [Batch Email API](https://resend.com/docs/api-reference/emails/send-batch-emails). Set the `batch` option to `true` to enable batch sending. Each email configuration is defined as an object within the `body` array.

```typescript
await client.publishJSON({
  api: {
    name: "email",
    provider: resend({ token: "<RESEND_TOKEN>", batch: true }),
  },
  body: [
    {
      from: "Acme <onboarding@resend.dev>",
      to: ["foo@gmail.com"],
      subject: "Hello World",
      html: "<h1>It works!</h1>",
    },
    {
      from: "Acme <onboarding@resend.dev>",
      to: ["bar@outlook.com"],
      subject: "World Hello",
      html: "<p>It works!</p>",
    },
  ],
});
```

Each entry in the `body` array represents an individual email, allowing customization of `from`, `to`, `subject`, `html`, and any other Resend-supported fields.

# Development Server License Agreement
Source: https://upstash.com/docs/qstash/misc/license

## 1. Purpose and Scope
This software is a development server implementation of QStash API ("Development Server") provided for testing and development purposes only. It is not intended for production use, commercial deployment, or as a replacement for the official QStash service.

## 2. Usage Restrictions
By using this Development Server, you agree to the following restrictions:

a) The Development Server may only be used for:
   * Local development and testing
   * Continuous Integration (CI) testing
   * Educational purposes
   * API integration development

b) The Development Server may NOT be used for:
   * Production environments
   * Commercial service offerings
   * Public-facing applications
   * Operating as a Software-as-a-Service (SaaS)
   * Reselling or redistributing as a service

## 3. Restrictions on Modification and Reverse Engineering
You may not:
* Decompile, reverse engineer, disassemble, or attempt to derive the source code of the Development Server
* Modify, adapt, translate, or create derivative works based upon the Development Server
* Remove, obscure, or alter any proprietary rights notices within the Development Server
* Attempt to bypass or circumvent any technical limitations or security measures in the Development Server

## 4. Technical Limitations
Users acknowledge that the Development Server:
* Operates entirely in-memory without persistence
* Provides limited functionality compared to the official service
* Offers no data backup or recovery mechanisms
* Has no security guarantees
* May have performance limitations
* Does not implement all features of the official service

## 5. Warranty Disclaimer
THE DEVELOPMENT SERVER IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. THE AUTHORS OR COPYRIGHT HOLDERS SHALL NOT BE LIABLE FOR ANY CLAIMS, DAMAGES, OR OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE IN VIOLATION OF THIS LICENSE.

## 6. Termination
Your rights under this license will terminate automatically if you fail to comply with any of its terms. Upon termination, you must cease all use of the Development Server.

## 7. Acknowledgment
By using the Development Server, you acknowledge that you have read this license, understand it, and agree to be bound by its terms.

# API Examples
Source: https://upstash.com/docs/qstash/overall/apiexamples

### Use QStash via:

* cURL
* [Typescript SDK](https://github.com/upstash/sdk-qstash-ts)
* [Python SDK](https://github.com/upstash/qstash-python)

Below are some examples to get you started. You can also check the [how to](/docs/qstash/howto/publishing) section for more technical details.

### Publish a message to an endpoint

Simple example to [publish](/docs/qstash/howto/publishing) a message to an endpoint.

<Tabs>
<Tab title="cURL">
```shell
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://example.com'
```
</Tab>

<Tab title="Typescript SDK">
```typescript
const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.publishJSON({
  url: "https://example.com",
  body: {
    hello: "world",
  },
});
```
</Tab>

<Tab title="Python SDK">
```python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://example.com",
    body={
        "hello": "world",
    },
)
# Async version is also available
```
</Tab>
</Tabs>

### Publish a message to a URL Group

The [URL Group](/docs/qstash/features/url-groups) is a way to publish a message to multiple endpoints in a
fan out pattern.

<Tabs>
<Tab title="cURL">
```shell
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/myUrlGroup'
```
</Tab>

<Tab title="Typescript SDK">
```typescript
const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.publishJSON({
  urlGroup: "myUrlGroup",
  body: {
    hello: "world",
  },
});
```
</Tab>

<Tab title="Python SDK">
```python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url_group="my-url-group",
    body={
        "hello": "world",
    },
)
# Async version is also available
```
</Tab>
</Tabs>

### Publish a message with 5 minutes delay

Add a delay to the message to be published. After QStash receives the message,
it will wait for the specified time (5 minutes in this example) before sending the message to the endpoint.

<Tabs>
<Tab title="cURL">
```shell
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -H "Upstash-Delay: 5m" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://example.com'
```
</Tab>

<Tab title="Typescript SDK">
```typescript
const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.publishJSON({
  url: "https://example.com",
  body: {
    hello: "world",
  },
  delay: 300,
});
```
</Tab>

<Tab title="Python SDK">
```python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://example.com",
    body={
        "hello": "world",
    },
    delay="5m",
)
# Async version is also available
```
</Tab>
</Tabs>

### Send a custom header

Add a custom header to the message to be published.

<Tabs>
<Tab title="cURL">
```shell
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H 'Upstash-Forward-My-Header: my-value' \
    -H "Content-type: application/json" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://example.com'
```
</Tab>

<Tab title="Typescript SDK">
```typescript
const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.publishJSON({
  url: "https://example.com",
  body: {
    hello: "world",
  },
  headers: {
    "My-Header": "my-value",
  },
});
```
</Tab>

<Tab title="Python SDK">
```python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://example.com",
    body={
        "hello": "world",
    },
    headers={
        "My-Header": "my-value",
    },
)
# Async version is also available
```
</Tab>
</Tabs>

### Schedule to run once a day

<Tabs>
<Tab title="cURL">
```shell
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Upstash-Cron: 0 0 * * *" \
    -H "Content-type: application/json" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/schedules/https://example.com'
```
</Tab>

<Tab title="Typescript SDK">
```typescript
const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.schedules.create({
  destination: "https://example.com",
  cron: "0 0 * * *",
});
```
</Tab>

<Tab title="Python SDK">
```python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.schedule.create(
    destination="https://example.com",
    cron="0 0 * * *",
)
# Async version is also available
```
</Tab>
</Tabs>

### Publish messages to a FIFO queue

By default, messges are published concurrently. With a [queue](/docs/qstash/features/queues), you can enqueue messages in FIFO order.

<Tabs>
<Tab title="cURL">
```shell
curl -XPOST -H 'Authorization: Bearer XXX' \
            -H "Content-type: application/json" \
            'https://qstash.upstash.io/v2/enqueue/my-queue/https://example.com'
            -d '{"message":"Hello, World!"}'
```
</Tab>

<Tab title="Typescript SDK">
```typescript
const client = new Client({ token: "<QSTASH_TOKEN>" });

const queue = client.queue({
  queueName: "my-queue"
})

await queue.enqueueJSON({
  url: "https://example.com",
  body: {
    "Hello": "World"
  }
})
```
</Tab>

<Tab title="Python SDK">
```python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.enqueue_json(
    queue="my-queue",
    url="https://example.com",
    body={
        "Hello": "World",
    },
)
# Async version is also available
```
</Tab>
</Tabs>

### Publish messages in a [batch](/docs/qstash/features/batch)

Publish multiple messages in a single request.

<Tabs>
<Tab title="cURL">
```shell
curl -XPOST https://qstash.upstash.io/v2/batch \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -d '
     [
      {
        "destination": "https://example.com/destination1"
      },
      {
        "destination": "https://example.com/destination2"
      }
     ]'
```
</Tab>

<Tab title="Typescript SDK">
```typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const res = await client.batchJSON([
  {
    url: "https://example.com/destination1",
  },
  {
    url: "https://example.com/destination2",
  },
]);
```
</Tab>

<Tab title="Python SDK">
```python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.batch_json(
    [
        {
            "url": "https://example.com/destination1",
        },
        {
            "url": "https://example.com/destination2",
        },
    ]
)
# Async version is also available
```
</Tab>
</Tabs>

### Set max retry count to 3

Configure how many times QStash should retry to send the message to the endpoint before
sending it to the [dead letter queue](/docs/qstash/features/dlq).

<Tabs>
<Tab title="cURL">
```shell
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Upstash-Retries: 3" \
    -H "Content-type: application/json" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://example.com'
```
</Tab>

<Tab title="Typescript SDK">
```typescript
const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.publishJSON({
  url: "https://example.com",
  body: {
    hello: "world",
  },
  retries: 3,
});
```
</Tab>

<Tab title="Python SDK">
```python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://example.com",
    body={
        "hello": "world",
    },
    retries=3,
)
# Async version is also available
```
</Tab>
</Tabs>

### Set custom retry delay

Configure the delay between retry attempts when message delivery fails. [By default, QStash uses exponential backoff](/docs/qstash/features/retry). You can customize this using mathematical expressions with the special variable `retried` (current retry attempt count starting from 0).

<Tabs>
<Tab title="cURL">
```shell
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Upstash-Retries: 3" \
    -H "Upstash-Retry-Delay: pow(2, retried) * 1000" \
    -H "Content-type: application/json" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://example.com'
```
</Tab>

<Tab title="Typescript SDK">
```typescript
const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.publishJSON({
  url: "https://example.com",
  body: {
    hello: "world",
  },
  retries: 3,
  retryDelay: "pow(2, retried) * 1000", // 2^retried * 1000ms
});
```
</Tab>

<Tab title="Python SDK">
```python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://example.com",
    body={
        "hello": "world",
    },
    retries=3,
    retry_delay="pow(2, retried) * 1000",  # 2^retried * 1000ms
)
# Async version is also available
```
</Tab>
</Tabs>

**Supported functions for retry delay expressions:**
* `pow` - Power function
* `sqrt` - Square root
* `abs` - Absolute value
* `exp` - Exponential
* `floor` - Floor function
* `ceil` - Ceiling function
* `round` - Rounding function
* `min` - Minimum of values
* `max` - Maximum of values

**Examples:**
* `1000` - Fixed 1 second delay
* `1000 * (1 + retried)` - Linear backoff: 1s, 2s, 3s, 4s...
* `pow(2, retried) * 1000` - Exponential backoff: 1s, 2s, 4s, 8s...
* `max(1000, pow(2, retried) * 100)` - Exponential with minimum 1s delay

### Set callback url

Receive a response from the endpoint and send it to the specified callback URL.
If the endpoint does not return a response, QStash will send it to the failure callback URL.

<Tabs>
<Tab title="cURL">
```shell
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-type: application/json" \
    -H "Upstash-Callback: https://example.com/callback" \
    -H "Upstash-Failure-Callback: https://example.com/failure" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://example.com'
```
</Tab>

<Tab title="Typescript SDK">
```typescript
const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.publishJSON({
  url: "https://example.com",
  body: {
    hello: "world",
  },
  callback: "https://example.com/callback",
  failureCallback: "https://example.com/failure",
});
```
</Tab>

<Tab title="Python SDK">
```python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(
    url="https://example.com",
    body={
        "hello": "world",
    },
    callback="https://example.com/callback",
    failure_callback="https://example.com/failure",
)
# Async version is also available
```
</Tab>
</Tabs>

### Get message logs

Retrieve logs for all messages that have been published (filtering is also available).

<Tabs>
<Tab title="cURL">
```shell
curl https://qstash.upstash.io/v2/logs \
    -H "Authorization: Bearer XXX"
```
</Tab>

<Tab title="Typescript SDK">
```typescript
const client = new Client({ token: "<QSTASH_TOKEN>" });
const logs = await client.logs()
```
</Tab>

<Tab title="Python SDK">
```python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.event.list()
# Async version is also available
```
</Tab>
</Tabs>

### List all schedules

<Tabs>
<Tab title="cURL">
```shell
curl https://qstash.upstash.io/v2/schedules \
    -H "Authorization: Bearer XXX"
```

</Tab>
<Tab title="Typescript SDK">
```typescript
const client = new Client({ token: "<QSTASH_TOKEN>" });
const scheds = await client.schedules.list();
```
</Tab>

<Tab title="Python SDK">
```python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.schedule.list()
# Async version is also available
```
</Tab>
</Tabs>

# Changelog
Source: https://upstash.com/docs/qstash/overall/changelog

<Note>
  We have moved the roadmap and the changelog to [Github Discussions](https://github.com/orgs/upstash/discussions) starting from October 2025.Now you can follow `In Progress` features. You can see that your `Feature Requests` are recorded. You can vote for them and comment your specific use-cases to shape the feature to your needs.
</Note>

<Update label="February 2026">
* **TypeScript SDK (`qstash-js`)** and **Python SDK (`qstash-py`):**
    * Added flow control management API: `list()`, `get(key)`, and `reset(key)` methods on `client.flowControl` (TypeScript) and `client.flow_control` (Python).
    * The `get` and `list` responses now include rich metrics: `waitListSize`, `parallelismMax`, `parallelismCount`, `rateMax`, `rateCount`, `ratePeriod`, and `ratePeriodStart`.
    * See the [Flow Control](/docs/qstash/features/flowcontrol#management-api) docs for code examples.
* **QStash Server:**
    * `GET /v2/flowControl` now supports an optional `search` query parameter to filter keys by name.
    * New `POST /v2/flowControl/:flowControlKey/reset` endpoint resets parallelism and rate counters for a key.
    * `GET /v2/globalParallelism` now returns `{ parallelismMax, parallelismCount }` instead of the old `{ waitListSize }` shape.
</Update>

<Update label="September 2025">
* **TypeScript SDK (`qstash-js`):**
    * `Label` feature is added. This will enable our users to label their publishes so that
        * Logs can be filtered with user given label.
        * DLQ can be filtered with user given label.
* **Console:**
    * `Flat view` on the `Logs` tab is removed. The purpose is to simplify the `Logs` tab.
    All the information is already available on the default(grouped) view. Let us know if there is something missing
    via Discord/Support so that we can fill in the gaps.
</Update>

<Update label="August 2025">
* **Console:**
    * Added ability to hide/show columns on the Schedules tab.
    * Local mode is added to enable our users to use the console with their local development envrionment. See [docs](/docs/qstash/howto/local-development) for details.
</Update>

<Update label="July 2025">
* **TypeScript SDK (`qstash-js`):**
    * Added `retryDelay` option to dynamicaly program the retry duration of a failed message.
        The new parameter is available in publish/batch/enqueue/schedules. See [here](/docs/qstash/features/retry#custom-retry-delay)
    * Full changelog, including all fixes, is available [here](https://github.com/upstash/qstash-js/compare/v2.8.1...v2.8.2).
</Update>

<Update label="June 2025">
    * No new features for QStash this month. We are mostly focused on stability and performance.
</Update>

<Update label="May 2025">
* **TypeScript SDK (`qstash-js`):**
    * Added `flow control period` and deprecated `ratePerSecond`. See [here](https://github.com/upstash/qstash-js/pull/237).
    * Added `IN_PROGRESS` state filter. See [here](https://github.com/upstash/qstash-js/pull/236).
    * Full changelog, including all fixes, is available [here](https://github.com/upstash/qstash-js/compare/v2.7.23...v2.8.1).
* **Python SDK (`qstash-py`):**
    * Added `IN_PROGRESS` state filter. See [here](https://github.com/upstash/qstash-js/pull/236).
    * Added various missing features: Callback Headers, Schedule with Queue, Overwrite Schedule ID, Flow Control Period. See [here](https://github.com/upstash/qstash-py/pull/41).
    * Full changelog, including all fixes, is available [here](https://github.com/upstash/qstash-py/compare/v2.0.5...v3.0.0).
* **Console:**
    * Improved logs tab behavior to prevent collapsing or unnecessary refreshes, increasing usability.
* **QStash Server:**
    * Added support for filtering messages by `FlowControlKey` (Console and SDK support in progress).
    * Applied performance improvements for bulk cancel operations.
    * Applied performance improvements for bulk publish operations.
    * Fixed an issue where scheduled publishes with queues would reset queue parallelism to 1.
    * Added support for updating existing queue parallelisms even when the max queue limit is reached.
    * Applied several additional performance optimizations.
</Update>

<Update label="April 2025">
* **QStash Server:**
    * Added support for `flow-control period`, allowing users to define a period for a given rate—up to 1 week.
      Previously, the period was fixed at 1 second.
      For example, `rate: 3 period: 1d` means publishes will be throttled to 3 per day.
    * Applied several performance optimizations.
* **Console:**
    * Added `IN_PROGRESS` as a filter option when grouping by message ID, making it easier to query in-flight messages.
      See [here](/docs/qstash/howto/debug-logs#lifecycle-of-a-message) for an explanation of message states.
</Update>

<Update label="March 2025">
* **TypeScript SDK (`qstash-js`):**
    * Renamed `events` to `logs` for clarity when referring to QStash features. `client.events()` is now deprecated, and `client.logs()` has been introduced. See [details here](https://github.com/upstash/qstash-js/pull/225).
    * For all fixes, see the full changelog [here](https://github.com/upstash/qstash-js/compare/v2.7.22...v2.7.23).
* **QStash Server:**
    * Fixed an issue where messages with delayed callbacks were silently failing. Now, such messages are explicitly rejected during insertion.
</Update>

<Update label="February 2025">
* **Python SDK (`qstash-py`):**
    * Flow Control Parallelism and Rate. See [here](https://github.com/upstash/qstash-py/pull/36)
    * Addressed a few minor bugs. See the full changelog [here](https://github.com/upstash/qstash-py/compare/v2.0.3...v2.0.5)
* **QStash Server:**
    * Introduced RateLimit and Parallelism controls to manage the rate and concurrency of message processing. Learn more [here](/docs/qstash/features/flowcontrol).
    * Improved connection timeout detection mechanism to enhance scalability.
    * Added several new features to better support webhook use cases:
        * Support for saving headers in a URL group. See [here](/docs/qstash/howto/webhook#2-url-group).
        * Ability to pass configuration parameters via query strings instead of headers. See [here](/docs/qstash/howto/webhook#1-publish).
        * Introduced a new `Upstash-Header-Forward` header to forward all headers from the incoming request. See [here](/docs/qstash/howto/webhook#1-publish).
</Update>

<Update label="January 2025">
* **Python SDK (`qstash-py`):**
    * Addressed a few minor bugs. See the full changelog [here](https://github.com/upstash/qstash-py/compare/v2.0.2...v2.0.3).
* **Local Development Server:**
    * The local development server is now publicly available. This server allows you to test your Qstash setup locally. Learn more about the local development server [here](/docs/qstash/howto/local-development).
* **Console:**
    * Separated the Workflow and QStash consoles for an improved user experience.
    * Separated their DLQ messages as well.
* **QStash Server:**
    * The core team focused on RateLimit and Parallelism features. These features are ready on the server and will be announced next month after the documentation and SDKs are completed.
</Update>

<Update label="December 2024">
* **TypeScript SDK (`qstash-js`):**
    * Added global headers to the client, which are automatically included in every publish request.
    * Resolved issues related to the Anthropics and Resend integrations.
    * Full changelog, including all fixes, is available [here](https://github.com/upstash/qstash-js/compare/v2.7.17...v2.7.20).

* **Python SDK (`qstash-py`):**
    * Introduced support for custom `schedule_id` values.
    * Enabled passing headers to callbacks using the `Upstash-Callback-Forward-...` prefix.
    * Full changelog, including all fixes, is available [here](https://github.com/upstash/qstash-py/compare/v2.0.0...v2.0.1).

* **Qstash Server:**
    * Finalized the local development server, now almost ready for public release.
    * Improved error reporting by including the field name in cases of invalid input.
    * Increased the maximum response body size for batch use cases to 100 MB per REST call.
    * Extended event retention to up to 14 days, instead of limiting to the most recent 10,000 events. Learn more on the [Pricing page](https://upstash.com/pricing/qstash).
</Update>

<Update label="November 2024">
* **TypeScript SDK (qstash-js):**
    * Added support for the Anthropics provider and refactored the `api` field of `publishJSON`. See the documentation [here](/docs/qstash/integrations/anthropic).
    * Full changelog, including fixes, is available [here](https://github.com/upstash/qstash-js/compare/v2.7.14...v2.7.17).
* **Qstash Server:**
    * Fixed a bug in schedule reporting. The Upstash-Caller-IP header now correctly reports the user’s IP address instead of an internal IP for schedules.
    * Validated the scheduleId parameter. The scheduleId must now be alphanumeric or include hyphens, underscores, or periods.
    * Added filtering support to bulk message cancellation. Users can now delete messages matching specific filters. See Rest API [here](/docs/qstash/api-reference/messages/bulk-cancel-messages).
    * Resolved a bug that caused the DLQ Console to become unusable when data was too large.
    * Fixed an issue with queues that caused them to stop during temporary network communication problems with the storage layer.
</Update>

<Update label="October 2024">
* **TypeScript SDK (qstash-js):**
    * Fixed a bug on qstash-js where we skipped using the next signing key when the current signing key fails to verify the `upstash-signature`. Released with qstash-js v2.7.14.
    * Added resend API. See [here](/docs/qstash/integrations/resend). Released with qstash-js v2.7.14.
    * Added `schedule to queues` feature to the qstash-js. See [here](/docs/qstash/features/schedules#scheduling-to-a-queue). Released with qstash-js v2.7.14.
* **Console:**
    * Optimized the console by trimming event bodies, reducing resource usage and enabling efficient querying of events with large payloads.
* **Qstash Server:**
    * Began development on a new architecture to deliver faster event processing on the server.
    * Added more fields to events in the [REST API](/docs/qstash/api-reference/logs/list-logs), including `Timeout`, `Method`, `Callback`, `CallbackHeaders`, `FailureCallback`, `FailureCallbackHeaders`, and `MaxRetries`.
    * Enhanced retry backoff logic by supporting additional headers for retry timing. Along with `Retry-After`, Qstash now recognizes `X-RateLimit-Reset`, `X-RateLimit-Reset-Requests`, and `X-RateLimit-Reset-Tokens` as backoff time indicators. See [here](/docs/qstash/features/retry#retry-after-headers) for more details.
</Update>

<Update label="September 2024">
* Improved performance, resulting in reduced latency for average publish times.
* Set the `nbf` (not before) claim on Signing Keys to 0. This claim specifies the time before which the JWT must not be processed. Previously, this was incorrectly used, causing validation issues when there were minor clock discrepancies between systems.
* Fixed queue name validation. Queue names must now be alphanumeric or include hyphens, underscores, or periods, consistent with other API resources.
* Resolved bugs related to [overwriting a schedule](/docs/qstash/features/schedules#overwriting-an-existing-schedule).
</Update>

<Update label="August 2024">
* Released [Upstash Workflow](/docs/qstash/workflow).
* Fixed a bug where paused schedules were mistakenly resumed after a process restart (typically occurring during new version releases).
</Update>

<Update label="July 2024">
* Big update on the UI, where all the Rest functinality exposed in the Console.
* Addded order query parameter to [/v2/events](/docs/qstash/api-reference/logs/list-logs) and [/v2/dlq](/docs/qstash/api-reference/dlq/list-dlq-messages) endpoints.
* Added [ability to configure](/docs/qstash/features/callbacks#configuring-callbacks) callbacks(/failure_callbacks)
* A critical fix for schedule pause and resume Rest APIs where the endpoints were not working at all before the fix.
</Update>

<Update label="June 2024">
* Pause and resume for scheduled messages
* Pause and resume for queues
* [Bulk cancel](/docs/qstash/api-reference/messages/bulk-cancel-messages) messages
* Body and headers on [events](/docs/qstash/api-reference/logs/list-logs)
* Fixed inaccurate queue lag
</Update>

<Update label="May 2024">
* [Retry-After](/docs/qstash/features/retry#retry-after-header) support for rate-limited endpoints
* [Upstash-Timeout](/docs/qstash/api-reference/messages/publish-a-message) header
</Update>

<Update label="April 2024">
* [Queues and parallelism](/docs/qstash/features/queues)
* [Event filtering](/docs/qstash/api-reference/logs/list-logs)
</Update>

<Update label="March 2024">
* [Batch publish messages](/docs/qstash/api-reference/messages/batch-messages)
* [Bulk delete](/docs/qstash/api-reference/dlq/bulk-delete-dlq-messages) for DLQ
</Update>

<Update label="February 2024">
* Added [failure callback support](/docs/qstash/api-reference/schedules/create-a-schedule) to scheduled messages
* Added Upstash-Caller-IP header to outgoing messages. See [https://upstash.com/docs/qstash/howto/receiving] for all headers
* Added Schedule ID to [events](/docs/qstash/api-reference/logs/list-logs) and [messages](/docs/qstash/api-reference/messages/get-a-message)
</Update>

<Update label="November 2023">
* Put last response in DLQ
* DLQ [get message](/docs/qstash/api-reference/dlq/delete-a-dlq-message)
* Pass schedule ID to the header when calling the user's endpoint
* Added more information to [callbacks](/docs/qstash/features/callbacks)
</Update>

<Update label="October 2023">
* Added [Upstash-Failure-Callback](/docs/qstash/features/callbacks#what-is-a-failure-callback)
</Update>

# Compare
Source: https://upstash.com/docs/qstash/overall/compare

In this section, we will compare QStash with alternative solutions.

### BullMQ

BullMQ is a message queue for NodeJS based on Redis. BullMQ is open source
project, you can run BullMQ yourself.

* Using BullMQ in serverless environments is problematic due to stateless nature
  of serverless. QStash is designed for serverless environments.

* With BullMQ, you need to run a stateful application to consume messages.
  QStash calls the API endpoints, so you do not need your application to consume
  messages continuously.

* You need to run and maintain BullMQ and Redis yourself. QStash is completely
  serverless, you maintain nothing and pay for just what you use.

### Zeplo

Zeplo is a message queue targeting serverless. Just like QStash it allows users
to queue and schedule HTTP requests.

While Zeplo targets serverless, it has a fixed monthly price in paid plans which
is \$39/month. In QStash, price scales to zero, you do not pay if you are not
using it.

With Zeplo, you can send messages to a single endpoint. With QStash, in addition
to endpoint, you can submit messages to a URL Group which groups one or more
endpoints into a single namespace. Zeplo does not have URL Group functionality.

### Quirrel

Quirrel is a job queueing service for serverless. It has a similar functionality
with QStash.

Quirrel is acquired by Netlify, some of its functionality is available as
Netlify scheduled functions. QStash is platform independent, you can use it
anywhere.

# Prod Pack & Enterprise
Source: https://upstash.com/docs/qstash/overall/enterprise

Upstash has Prod Pack and Enterprise plans for customers with critical production workloads. Prod Pack and Enterprise plans include additional monitoring and security features in addition to higher capacity limits and more powerful resources.

Prod Pack add-on is available for both pay-as-you-go and fixed-price plans. Enterprise plans are custom plans with additional features and higher limits.

All features of Prod Pack and Enterprise plan for Upstash QStash are detailed below.

## How to Upgrade

You can activate Prod Pack in the QStash settings page in the [Upstash Console](https://console.upstash.com/qstash). For the Enterprise plan, please create a request through the Upstash Console or contact [support@upstash.com](mailto:support@upstash.com).

# Prod Pack Features
Below QStash features are enabled with Prod Pack.

### Uptime SLA
All Prod Pack accounts come with an SLA guaranteeing 99.99% uptime. For mission-critical messaging where uptime is crucial, we recommend Prod Pack plans. Learn more about [Uptime SLA](/docs/common/help/sla).

### SOC-2 Type 2 Compliance & Report
Upstash QStash is SOC-2 Type 2 compliant with Prod Pack. Once you enable Prod Pack, you can request access to the report by going to [Upstash Trust Center](https://trust.upstash.com/) or contacting [support@upstash.com](mailto:support@upstash.com).

### Encryption at Rest
Encrypts the storage where your QStash message data is persisted and stored.

### Prometheus Metrics

Prometheus is an open-source monitoring system widely used for monitoring and alerting in cloud-native and containerized environments.

Upstash Prod Pack and Enterprise plans offer Prometheus metrics collection, enabling you to monitor your QStash messages with Prometheus in addition to console metrics. Learn more about [Prometheus integration](/docs/qstash/integrations/prometheus).

### Datadog Integration

Upstash Prod Pack and Enterprise plans include integration with Datadog, allowing you to monitor your QStash messages with Datadog in addition to console metrics. Learn more about [Datadog integration](/docs/qstash/integrations/datadog).

# Enterprise Features

All Prod Pack features are included in the Enterprise plan. Additionally, Enterprise plans include:

### 100M+ Messages Daily
Enterprise plans support 100 million or more messages per day, suitable for high-volume production workloads.

### Unlimited Bandwidth
Enterprise plans include unlimited bandwidth, ensuring no data transfer limits for your messaging needs.

### SAML SSO
Single Sign-On (SSO) allows you to use your existing identity provider to authenticate users for your Upstash account. This feature is available upon request for Enterprise customers.

### Professional Support with SLA
Enterprise plans include access to our professional support with response time SLAs and priority access to our support team. Check out the [support page](/docs/common/help/prosupport) for more details.

### Dedicated Resources for Isolation
Enterprise customers receive dedicated resources to ensure isolation and consistent performance for their messaging workloads.

# Getting Started
Source: https://upstash.com/docs/qstash/overall/getstarted

import { Catalog } from "/snippets/catalog.jsx";

QStash is a **serverless messaging and scheduling solution**. It fits easily into your existing workflow and allows you to build reliable systems without managing infrastructure.

Instead of calling an endpoint directly, QStash acts as a middleman between you and an API to guarantee delivery, perform automatic retries on failure, and more.

  <img />

<Tip href="/workflow/getstarted" >

We have a new SDK called [Upstash Workflow](/docs/workflow/getstarted).

**Upstash Workflow SDK** is **QStash** simplified for your complex applications

* Skip the details of preparing a complex dependent endpoints.
* Focus on the essential parts.
* Enjoy automatic retries and delivery guarantees.
* Avoid platform-specific timeouts.

Check out [Upstash Workflow Getting Started](/docs/workflow/getstarted) for more.
</Tip>

## Quick Start

Check out these Quick Start guides to get started with QStash in your application.

<CardGroup cols={2}>
  <Card title="Next.js" icon="node-js" href="/qstash/quickstarts/vercel-nextjs">
    Build a Next application that uses QStash to start a long-running job on your platform
  </Card>
  <Card title="Python" icon="python" href="/qstash/quickstarts/python-vercel">
    Build a Python application that uses QStash to schedule a daily job that clean up a database
  </Card>
</CardGroup>

Or continue reading to learn how to send your first message!

## Send your first message

<Check>
**Prerequisite**

You need an Upstash account before publishing messages, create one
[here](https://console.upstash.com).

</Check>

### Public API

Make sure you have a publicly available HTTP API that you want to send your
messages to. If you don't, you can use something like
[requestcatcher.com](https://requestcatcher.com/), [webhook.site](https://webhook.site/) or
[webhook-test.com](https://webhook-test.com/) to try it out.

For example, you can use this URL to test your messages: [https://firstqstashmessage.requestcatcher.com](https://firstqstashmessage.requestcatcher.com)

### Get your QStash Token

Go to the [QStash tab in the Upstash Console](https://console.upstash.com/qstash),
pick the region you want to use (see [Select a Region](/docs/qstash/howto/multi-region)),
and copy the `QSTASH_TOKEN` from the **Quickstart** section.

  <img />

### Publish a message

A message can be any shape or form: json, xml, binary, anything, that can be
transmitted in the http request body. We do not impose any restrictions other
than a size limit of 1 MB (which can be customized at your request).

In addition to the request body itself, you can also send HTTP headers. Learn
more about this in the [message publishing section](/docs/qstash/howto/publishing).

<CodeGroup>
```bash cURL
curl -XPOST \
    -H 'Authorization: Bearer <QSTASH_TOKEN>' \
    -H "Content-type: application/json" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://<your-api-url>'
```

```bash cURL RequestCatcher
curl -XPOST \
    -H 'Authorization: Bearer <QSTASH_TOKEN>' \
    -H "Content-type: application/json" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://firstqstashmessage.requestcatcher.com/test'
```

</CodeGroup>

Don't worry, we have SDKs for different languages so you don't
have to make these requests manually.

### Check Response

You should receive a response with a unique message ID.

  <img />

### Check Message Status

Head over to [Upstash Console](https://console.upstash.com/qstash) and go to the
`Logs` tab where you can see your message activities.

  <img />

Learn more about different states [here](/docs/qstash/howto/debug-logs).

## Features and Use Cases

<CardGroup cols={2}>
  <Card title="Background Jobs" icon="share-all" href="/qstash/features/background-jobs">
    Run long-running tasks in the background, without blocking your application
  </Card>
  <Card title="Schedules" icon="calendar-days" href="/qstash/features/schedules">
    Schedule messages to be delivered at a time in the future
  </Card>
  <Card title="Fan out" icon="arrows-maximize" href="/qstash/features/url-groups">
    Publish messages to multiple endpoints, in parallel, using URL Groups
  </Card>
  <Card title="FIFO" icon="right-left" href="/qstash/features/queues#ordered-delivery">
    Enqueue messages to be delivered one by one in the order they have enqueued.
  </Card>
  <Card
    title="Flow Control"
    icon="arrows-up-to-line"
    href="/qstash/features/flowcontrol"
  >
    Custom rate per second and parallelism limits to avoid overflowing your endpoint.
  </Card>
  <Card title="Callbacks" icon="phone" href="/qstash/features/callbacks">
    Get a response delivered to your API when a message is delivered
  </Card>
  <Card title="Retry Failed Jobs" icon="repeat" href="/qstash/features/dlq">
    Use a Dead Letter Queue to have full control over failed messages
  </Card>
  <Card title="Deduplication" icon="copy" href="/qstash/features/deduplication">
    Prevent duplicate messages from being delivered
  </Card>
  <Card
    title="LLM Integrations"
    icon="shapes"
    href="/qstash/integrations/llm"
  >
    Publish, enqueue, or batch chat completion requests using large language models with QStash
    features.
  </Card>
</CardGroup>

## SDKs

<Catalog product="qstash" type="sdk" cols={3} />

# llms.txt
Source: https://upstash.com/docs/qstash/overall/llms-txt

# Pricing & Limits
Source: https://upstash.com/docs/qstash/overall/pricing

Please check our [pricing page](https://upstash.com/pricing/qstash) for the most up-to-date information on pricing and limits.

# Roadmap
Source: https://upstash.com/docs/qstash/overall/roadmap

<Note>
  We have moved the roadmap and the changelog to [Github Discussions](https://github.com/orgs/upstash/discussions) starting from October 2025.Now you can follow `In Progress` features. You can see that your `Feature Requests` are recorded. You can vote for them and comment your specific use-cases to shape the feature to your needs.
</Note>

# Use Cases
Source: https://upstash.com/docs/qstash/overall/usecases

QStash is an HTTP-based messaging and scheduling service. You hand it a request,
and QStash delivers it to your endpoint later — with retries, delays, ordering,
rate limits, and a dead letter queue when things go wrong.

That makes it a fit for any work that shouldn't happen inside the request that
triggered it: tasks that take too long, tasks that must survive a failure, tasks
that must run on a schedule, and tasks that must not overwhelm the service they
call.

Because everything is HTTP, there is no consumer to keep running. Your existing
API endpoints *are* the consumers, wherever they are deployed — Vercel, AWS
Lambda, Cloudflare Workers, Fly.io, or your own servers.

## Background jobs

Serverless platforms cap how long a function can run. Anything heavier than a
few seconds — video processing, report generation, importing a CSV, calling a
slow third-party API — risks a timeout, and the user is waiting for it.

With QStash, your handler publishes a message and returns immediately. QStash
calls a second endpoint that does the real work, retrying if it fails.

```typescript
import { Client } from "@upstash/qstash";

const client = new Client({ token: process.env.QSTASH_TOKEN! });

await client.publishJSON({
  url: "https://your-app.com/api/process-video",
  body: { videoId },
  retries: 3,
});
```

If the job itself is longer than a single function invocation allows, use
[callbacks](/docs/qstash/features/callbacks) so QStash delivers the response to
another endpoint once it's ready, instead of your caller blocking on it.

<Card
  title="Background Jobs"
  icon="share-all"
  href="/qstash/features/background-jobs"
>
  Full walkthrough, including local development
</Card>

## Scheduled and recurring tasks

Anything you would put in a cron job — nightly reports, resetting billing
cycles, expiring trials, syncing a search index, warming a cache — becomes a
[schedule](/docs/qstash/features/schedules) that calls your endpoint on a cron
expression.

```typescript
await client.schedules.create({
  destination: "https://your-app.com/api/daily-report",
  cron: "0 8 * * *",
});
```

Schedules run in UTC by default and support
[timezones](/docs/qstash/features/schedules#timezones). Unlike platform-native cron
(such as Vercel Cron), schedules are not tied to a deploy, are not limited to
one per plan tier, and retry on failure.

## Reliable webhook delivery

Webhooks are the most common reason people reach for QStash, in both
directions:

**Receiving webhooks.** Point Stripe, GitHub, Shopify, or Clerk at a QStash
publish URL instead of your endpoint directly. QStash absorbs the burst, retries
if your app is down or mid-deploy, and applies whatever delay, timeout, or
[flow control](/docs/qstash/features/flowcontrol) you configure. The provider gets a
fast 2xx even when your processing is slow.

**Sending webhooks.** If you deliver webhooks to your own customers, QStash
handles the part nobody wants to build: exponential retries, per-customer
concurrency limits, and a [dead letter queue](/docs/qstash/features/dlq) for
endpoints that stay down.

<CardGroup cols={2}>
  <Card title="Use as Webhook Receiver" icon="webhook" href="/qstash/howto/webhook">
    Publish URLs, URL Groups, and header forwarding
  </Card>
  <Card
    title="Building Reliable & Type-Safe Webhooks"
    icon="book"
    href="https://upstash.com/blog/webhook-system-with-qstash"
  >
    Designing an outbound webhook system on QStash
  </Card>
</CardGroup>

## Fan-out to multiple services

One event often needs to reach several places: a purchase should trigger a
receipt email, a Slack notification, an analytics event, and a warehouse
webhook.

Publish once to a [URL Group](/docs/qstash/features/url-groups) and QStash creates an
independent, independently-retried delivery for each subscribed endpoint. Adding
or removing a consumer is a URL Group change — no redeploy of the producer.

```typescript
await client.publishJSON({
  urlGroup: "order-created",
  body: { orderId },
});
```

The same shape works for alerting: one alert source fanned out to Slack, email,
and PagerDuty.

## Rate-limited and fragile third-party APIs

When you call an API with a quota — OpenAI, Resend, Shopify, a partner's
internal service — the hard part is not calling it, it's not calling it too
often. [Flow Control](/docs/qstash/features/flowcontrol) lets QStash hold messages
back for you, by request rate, by concurrency, or both.

```typescript
await client.publishJSON({
  url: "https://your-app.com/api/summarize",
  body: { articleId },
  flowControl: { key: "openai", parallelism: 5, rate: 60, period: "1m" },
});
```

You can publish ten thousand messages at once and let QStash drip them out at
the rate your downstream tolerates, instead of building a queue and a limiter
yourself. Limits apply per key, so the same key can span multiple URLs.

<Card
  title="Efficient Article Summarization with QStash"
  icon="book"
  href="https://upstash.com/blog/article-summarizer-qstash-python"
>
  Handling API rate limits and parallel processing in Python
</Card>

## AI and LLM requests

LLM calls are slow, variable, and expensive to retry by hand — a bad match for a
10-second serverless timeout. QStash gives them a 2-hour HTTP timeout, delivers
the response to a [callback](/docs/qstash/features/callbacks) endpoint when it's
done, and can [batch](/docs/qstash/features/batch) many requests in one publish.

There are built-in integrations for [OpenAI-compatible
providers](/docs/qstash/integrations/llm) and [Anthropic](/docs/qstash/integrations/anthropic),
so QStash calls the provider for you and you only handle the callback.

Combined with flow control, this is a practical way to run bulk embedding jobs,
document summarization, or content generation without hitting provider rate
limits.

## Delayed and time-based messages

Some work is defined by *when* it should happen: a welcome email 10 minutes
after signup, a trial-ending reminder 3 days out, an abandoned-cart nudge, a
retry of a payment tomorrow.

[Delay](/docs/qstash/features/delay) a message by a duration or to an absolute
timestamp, and QStash holds it until then — up to 7 days on the free plan and up
to a year on pay-as-you-go.

```typescript
await client.publishJSON({
  url: "https://your-app.com/api/send-welcome-email",
  body: { userId },
  delay: "10m",
});
```

With the [Resend integration](/docs/qstash/integrations/resend) you can skip the
endpoint entirely and have QStash send the email itself at the scheduled time.

<CardGroup cols={2}>
  <Card
    title="Scheduling emails in the user's timezone"
    icon="book"
    href="https://upstash.com/blog/timezone-scheduling-emails"
  >
    Per-user send times with QStash
  </Card>
  <Card
    title="Building an Email Scheduler"
    icon="book"
    href="https://upstash.com/blog/email-scheduler-qstash-python"
  >
    An email scheduler with the Python SDK
  </Card>
</CardGroup>

## Ordered processing

Some pipelines break if messages overtake each other — applying a sequence of
updates to the same record, processing a customer's events in order, or writing
to a system that can't handle concurrent writes.

[Queues](/docs/qstash/features/queues) deliver messages one at a time in FIFO order.
The next message only becomes active after the current one is delivered, has
exhausted its retries, or its callback has finished.

```typescript
const queue = client.queue({ queueName: "user-123-events" });

await queue.enqueueJSON({
  url: "https://your-app.com/api/apply-event",
  body: { event },
});
```

## Syncing and periodic data updates

Instead of querying a slow or rate-limited third-party API on every request,
schedule a job that pulls fresh data into your own database, and serve reads
from there. The same pattern covers flushing Redis state to a primary database,
refreshing a cache, and rebuilding a search index.

<CardGroup cols={2}>
  <Card
    title="Periodic Data Updates"
    icon="rotate"
    href="/qstash/recipes/periodic-data-updates"
  >
    Recipe: keep third-party data fresh in your own database
  </Card>
  <Card
    title="Sync Redis state to your database"
    icon="book"
    href="https://upstash.com/blog/syncing-state-with-qstash"
  >
    Write-behind from Redis using QStash
  </Card>
</CardGroup>

## Decoupling services

Beyond individual jobs, QStash works as the messaging layer between your
services: producers publish, QStash guarantees
[at-least-once delivery](/docs/qstash/features/at-least-once), and consumers are just
HTTP endpoints. [Deduplication](/docs/qstash/features/deduplication) keeps retries
from double-processing, [signature verification](/docs/qstash/features/security)
proves a request came from QStash, and the DLQ holds anything that never
succeeded.

This is the pattern behind cutting serverless costs, too: move expensive work
out of long-running function invocations and let QStash drive short, cheap ones.

<Card
  title="Get Rid of Function Timeouts and Reduce Vercel Costs"
  icon="book"
  href="https://upstash.com/blog/vercel-cost-workflow"
>
  Why offloading work changes your bill
</Card>

## Multi-step workflows

If your task has several dependent steps — call an API, wait for a human,
branch, then call another — chaining QStash messages by hand gets awkward.
[Upstash Workflow](/docs/workflow/getstarted) is built on QStash and gives you
durable, resumable functions where each step is checkpointed automatically.

<Tip href="/workflow/getstarted">
Use QStash directly for single messages, schedules, and fan-out. Reach for
[Upstash Workflow](/docs/workflow/getstarted) when the logic spans multiple dependent
steps.
</Tip>

## More examples

<CardGroup cols={2}>
  <Card
    title="Building a seriously reliable serverless API"
    icon="book"
    href="https://upstash.com/blog/build-reliable-serverless-api"
  >
    Retries, idempotency, and failure handling end to end
  </Card>
  <Card
    title="Decouple Webhook Processing on Next.js"
    icon="book"
    href="https://upstash.com/blog/webhook-qstash"
  >
    Taking webhook work off the request path
  </Card>
  <Card
    title="Build a Subscription Service with Next.js & Prisma"
    icon="book"
    href="https://upstash.com/blog/saas-subscription"
  >
    Recurring billing cycles driven by schedules
  </Card>
  <Card
    title="Refresh stale data in a SvelteKit app"
    icon="book"
    href="https://upstash.com/blog/sveltekit-qstash"
  >
    Scheduled revalidation outside the request path
  </Card>
  <Card
    title="Serverless Background Jobs and Message Queues Compared"
    icon="scale-balanced"
    href="https://upstash.com/blog/serverless-background-jobs-and-message-queues-every-major-option-in-2026"
  >
    How QStash compares to the alternatives
  </Card>
  <Card
    title="Why We Chose QStash at Scale"
    icon="book"
    href="https://upstash.com/blog/qstash-workflow-at-scale"
  >
    A production user's account of running QStash
  </Card>
</CardGroup>

More posts are on the [QStash blog](https://upstash.com/blog/tag/qstash). If
there's a use case you'd like documented, tell us on
[Discord](https://upstash.com/discord) or [X](https://x.com/upstash).

- [AWS Lambda (Node)](https://upstash.com/docs/qstash/quickstarts/aws-lambda/nodejs.md)
- [AWS Lambda (Python)](https://upstash.com/docs/qstash/quickstarts/aws-lambda/python.md)
- [Cloudflare Workers](https://upstash.com/docs/qstash/quickstarts/cloudflare-workers.md)
- [Deno Deploy](https://upstash.com/docs/qstash/quickstarts/deno-deploy.md)
- [Golang](https://upstash.com/docs/qstash/quickstarts/fly-io/go.md)
- [Python on Vercel](https://upstash.com/docs/qstash/quickstarts/python-vercel.md)
- [Next.js](https://upstash.com/docs/qstash/quickstarts/vercel-nextjs.md)
- [Periodic Data Updates](https://upstash.com/docs/qstash/recipes/periodic-data-updates.md)
- [DLQ](https://upstash.com/docs/qstash/sdks/py/examples/dlq.md)
- [Events](https://upstash.com/docs/qstash/sdks/py/examples/events.md)
- [Flow Control](https://upstash.com/docs/qstash/sdks/py/examples/flow-control.md)
- [Keys](https://upstash.com/docs/qstash/sdks/py/examples/keys.md)
- [Messages](https://upstash.com/docs/qstash/sdks/py/examples/messages.md)
- [Overview](https://upstash.com/docs/qstash/sdks/py/examples/overview.md)
- [Publish](https://upstash.com/docs/qstash/sdks/py/examples/publish.md)
- [Queues](https://upstash.com/docs/qstash/sdks/py/examples/queues.md)
- [Receiver](https://upstash.com/docs/qstash/sdks/py/examples/receiver.md)
- [Schedules](https://upstash.com/docs/qstash/sdks/py/examples/schedules.md)
- [URL Groups](https://upstash.com/docs/qstash/sdks/py/examples/url-groups.md)

# Getting Started
Source: https://upstash.com/docs/qstash/sdks/py/gettingstarted

## Install

### PyPI

```bash
pip install qstash
```

## Get QStash token

Follow the instructions [here](/docs/qstash/overall/getstarted) to get your QStash token and signing keys.

## Usage

#### Synchronous Client

```python
from qstash import QStash

client = QStash("<QSTASH_TOKEN>")
client.message.publish_json(...)
```

#### Asynchronous Client

```python
import asyncio

from qstash import AsyncQStash

async def main():
    client = AsyncQStash("<QSTASH_TOKEN>")
    await client.message.publish_json(...)

asyncio.run(main())
```

#### RetryConfig

You can configure the retry policy of the client by passing the configuration to the client constructor.

Note: This isn for sending the request to QStash, not for the retry policy of QStash.

The default number of retries is **5** and the default backoff function is `lambda retry_count: math.exp(retry_count) * 50`.

You can also pass in `False` to disable retrying.

```python
from qstash import QStash

client = QStash(
    "<QSTASH_TOKEN>",
    retry={
        "retries": 3,
        "backoff": lambda retry_count: (2**retry_count) * 20,
    },
)
```

# Overview
Source: https://upstash.com/docs/qstash/sdks/py/overview

`qstash` is an Python SDK for QStash, allowing for easy access to the QStash API.

Using `qstash` you can:

* Publish a message to a URL/URL group/API
* Publish a message with a delay
* Schedule a message to be published
* Access logs for the messages that have been published
* Create, read, update, or delete URL groups.
* Read or remove messages from the [DLQ](/docs/qstash/features/dlq)
* Read or cancel messages
* Verify the signature of a message

You can find the Github Repository [here](https://github.com/upstash/qstash-py).

- [DLQ](https://upstash.com/docs/qstash/sdks/ts/examples/dlq.md)
- [Flow Control](https://upstash.com/docs/qstash/sdks/ts/examples/flow-control.md)
- [Logs](https://upstash.com/docs/qstash/sdks/ts/examples/logs.md)
- [Messages](https://upstash.com/docs/qstash/sdks/ts/examples/messages.md)
- [Overview](https://upstash.com/docs/qstash/sdks/ts/examples/overview.md)
- [Publish](https://upstash.com/docs/qstash/sdks/ts/examples/publish.md)
- [Queues](https://upstash.com/docs/qstash/sdks/ts/examples/queues.md)
- [Receiver](https://upstash.com/docs/qstash/sdks/ts/examples/receiver.md)
- [Schedules](https://upstash.com/docs/qstash/sdks/ts/examples/schedules.md)
- [URL Groups](https://upstash.com/docs/qstash/sdks/ts/examples/url-groups.md)

# Getting Started
Source: https://upstash.com/docs/qstash/sdks/ts/gettingstarted

## Install

### NPM

```bash
npm install @upstash/qstash
```

## Get QStash token

Follow the instructions [here](/docs/qstash/overall/getstarted) to get your QStash token and signing keys.

## Usage

```typescript
import { Client } from "@upstash/qstash";

const client = new Client({
  token: "<QSTASH_TOKEN>",
});
```

#### RetryConfig

You can configure the retry policy of the client by passing the configuration to the client constructor.

Note: This is for sending the request to QStash, not for the retry policy of QStash.

The default number of attempts is **6** and the default backoff function is `(retry_count) => (Math.exp(retry_count) * 50)`.

You can also pass in `false` to disable retrying.

```typescript
import { Client } from "@upstash/qstash";

const client = new Client({
  token: "<QSTASH_TOKEN>",
  retry: {
    retries: 3,
    backoff: retry_count => 2 ** retry_count * 20,
  },
});
```

## Local development

Pass `devMode: true` (or set `QSTASH_DEV=true`) to have the SDK download, start, and manage a local QStash dev server for you. No tokens or signing keys required — the SDK injects deterministic dev credentials.

```typescript
import { Client } from "@upstash/qstash";

const client = new Client({ devMode: true });
```

The same flag works on the receiving side — pass `devMode: true` to `Receiver` or `verifySignature*` to verify signatures with the dev server's keys. See [Local Development](/docs/qstash/howto/local-development) for the full walkthrough, including the `registerQStashDev()` helper for Next.js edge routes.

## Telemetry

This sdk sends anonymous telemetry headers to help us improve your experience.
We collect the following:

* SDK version
* Platform (Cloudflare, AWS or Vercel)
* Runtime version (node@18.x)

You can opt out by setting the `UPSTASH_DISABLE_TELEMETRY` environment variable
to any truthy value. Or setting `enableTelemetry: false` in the client options.

```ts
const client = new Client({
  token: "<QSTASH_TOKEN>",
  enableTelemetry: false,
});
```

# Overview
Source: https://upstash.com/docs/qstash/sdks/ts/overview

`@upstash/qstash` is a Typescript SDK for QStash, allowing for easy access to the QStash API.

Using `@upstash/qstash` you can:

* Publish a message to a URL/URL Group
* Publish a message with a delay
* Schedule a message to be published
* Access logs for the messages that have been published
* Create, read, update, or delete URL groups.
* Read or remove messages from the [DLQ](/docs/qstash/features/dlq)
* Read or cancel messages
* Verify the signature of a message

You can find the Github Repository [here](https://github.com/upstash/sdk-qstash-ts).

# Channels
Source: https://upstash.com/docs/realtime/features/channels

Channels allow you to scope events to specific people or rooms. For example:

* Chat rooms
* Emitting events to a specific user

## Default Channel

By default, events are sent to the `default` channel. If we emit an event without specifying a channel like so:

```typescript
await realtime.emit("notification.alert", "hello world!")
```

it can automatically be read using the default channel:

```typescript
useRealtime({
  events: ["notification.alert"],
  onData({ event, data, channel }) {
    console.log(data)
  },
})
```

***

## Custom Channels

Emit events to a specific channel:

```typescript route.ts
const channel = realtime.channel("user-123")

await channel.emit("notification.alert", "hello world!")
```

Subscribe to one or more channels:

```tsx page.tsx
"use client"

import { useRealtime } from "@/lib/realtime-client"

export default function Page() {
  useRealtime({
    channels: ["user-123"],
    events: ["notification.alert"],
    onData({ event, data, channel }) {
      console.log(data)
    },
  })

  return <>...</>
}
```

## Channel Patterns

<AccordionGroup>
  <Accordion title="User-Specific Channels">
    Send notifications to individual users:

    ```typescript route.ts
    const channel = realtime.channel(`user-${userId}`)

    await channel.emit("notification.alert", "hello world!")
    ```

    ```typescript page.tsx
    useRealtime({
      channels: [`user-${user.id}`],
      events: ["notification.alert"],
      onData({ data }) {},
    })
    ```

  </Accordion>

  <Accordion title="Room-Based Channels">
    Broadcast to all users in a room:

    ```typescript route.ts
    await realtime.channel(`room-${roomId}`).emit("room.message", {
      text: "Hello everyone!",
      sender: "Alice",
    })
    ```

  </Accordion>

  <Accordion title="Team Workspaces">
    Scope events to team workspaces:

    ```typescript route.ts
    await realtime.channel(`team-${teamId}`).emit("project.update", {
      project: "Website Redesign",
      status: "In Progress",
    })
    ```

  </Accordion>
</AccordionGroup>

## Dynamic Channels

Subscribe to multiple channels at the same time:

```tsx page.tsx
"use client"

import { useState } from "react"
import { useRealtime } from "@/lib/realtime-client"

export default function Page() {
  const [channels, setChannels] = useState<string[]>(["lobby"])

  useRealtime({
    channels,
    events: ["chat.message"],
    onData({ event, data, channel }) {
      console.log(`Message from ${channel}:`, data)
    },
  })

  const joinRoom = (roomId: string) => {
    setChannels((prev) => [...prev, roomId])
  }

  const leaveRoom = (roomId: string) => {
    setChannels((prev) => prev.filter((c) => c !== roomId))
  }

  return (
    <div>
      <p>Active channels: {channels.join(", ")}</p>
      <button onClick={() => joinRoom("room-1")}>Join Room 1</button>
      <button onClick={() => joinRoom("room-2")}>Join Room 2</button>
      <button onClick={() => leaveRoom("lobby")}>Leave Lobby</button>
    </div>
  )
}
```

## Broadcasting to Multiple Channels

Emit to multiple channels at the same time:

```typescript route.ts
const rooms = ["lobby", "room-1", "room-2"]

await Promise.all(
  rooms.map((room) => {
    const channel = realtime.channel(room)
    return channel.emit("chat.message", `Hi channel ${room}!`)
  })
)
```

## Channel Security

Combine channels with [middleware](/docs/realtime/features/middleware) for secure access control:

```typescript title="app/api/realtime/route.ts"
import { handle } from "@upstash/realtime"
import { realtime } from "@/lib/realtime"
import { currentUser } from "@/auth"

export const GET = handle({
  realtime,
  middleware: async ({ request, channels }) => {
    const user = await currentUser(request)

    for (const channel of channels) {
      if (!user.canAccessChannel(channel)) {
        return new Response("Unauthorized", { status: 401 })
      }
    }
  },
})
```

<Card
  title="Authenticate Realtime Requests"
  icon="shield"
  href="/realtime/features/middleware"
>
  See the middleware documentation for authentication examples
</Card>

# Client-Side Usage
Source: https://upstash.com/docs/realtime/features/client-side

The `useRealtime` hook connects your React components to realtime events with full type safety.

## Setup

### 1. Add the Provider

Wrap your app in the `RealtimeProvider`:

```tsx providers.tsx
"use client"

import { RealtimeProvider } from "@upstash/realtime/client"

export function Providers({ children }: { children: React.ReactNode }) {
  return <RealtimeProvider>{children}</RealtimeProvider>
}
```

```tsx layout.tsx
import { Providers } from "./providers"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}
```

### 2. Create Typed Hook

Create a typed `useRealtime` hook using `createRealtime`:

```typescript lib/realtime-client.ts
"use client"

import { createRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "./realtime"

export const { useRealtime } = createRealtime<RealtimeEvents>()
```

## Basic Usage

Subscribe to events in any client component:

```tsx page.tsx
"use client"

import { useRealtime } from "@/lib/realtime-client"

export default function Page() {
  useRealtime({
    events: ["notification.alert"],
    onData({ event, data, channel }) {
      console.log(`Received ${event}:`, data)
    },
  })

  return <p>Listening for events...</p>
}
```

## Provider Options

<ParamField
  path="api"
  type="object"
  default='{ url: "/api/realtime", withCredentials: false }'
>
  API configuration: - `url`: The realtime endpoint URL - `withCredentials`: Whether to
  send cookies with requests
</ParamField>

<ParamField path="maxReconnectAttempts" type="number" default="3">
  Maximum number of reconnection attempts before giving up
</ParamField>

```tsx providers.tsx
"use client"

import { RealtimeProvider } from "@upstash/realtime/client"

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <RealtimeProvider
      api={{ url: "/api/realtime", withCredentials: true }}
      maxReconnectAttempts={5}
    >
      {children}
    </RealtimeProvider>
  )
}
```

## Hook Options

<ParamField path="events" type="string[]">
  Array of event names to subscribe to (e.g. `["notification.alert", "chat.message"]`)
</ParamField>

<ParamField path="onData" type="function">
  Callback when an event is received. Receives an object with `event`, `data`, and
  `channel`.
</ParamField>

<ParamField path="channels" type="string[]" default='["default"]'>
  Array of channel names to subscribe to
</ParamField>

<ParamField path="enabled" type="boolean" default="true">
  Whether the subscription is active. Set to `false` to disconnect.
</ParamField>

## Return Value

The hook returns an object with:

<ResponseField name="status" type="ConnectionStatus">
  Current connection state: `"connecting"`, `"connected"`, `"disconnected"`, or `"error"`
</ResponseField>

```tsx page.tsx
import { useRealtime } from "@/lib/realtime-client"

const { status } = useRealtime({
  events: ["notification.alert"],
  onData({ event, data, channel }) {},
})

console.log(status)
```

## Connection Control

Enable or disable connections dynamically:

```tsx page.tsx
"use client"

import { useState } from "react"
import { useRealtime } from "@/lib/realtime-client"

export default function Page() {
  const [enabled, setEnabled] = useState(true)

  const { status } = useRealtime({
    enabled,
    events: ["notification.alert"],
    onData({ event, data, channel }) {
      console.log(event, data, channel)
    },
  })

  return (
    <div>
      <button onClick={() => setEnabled((prev) => !prev)}>
        {enabled ? "Disconnect" : "Connect"}
      </button>

      <p>Status: {status}</p>
    </div>
  )
}
```

### Conditional Connections

Connect only when certain conditions are met:

```tsx page.tsx
"use client"

import { useRealtime } from "@/lib/realtime-client"
import { useUser } from "@/hooks/auth"

export default function Page() {
  const { user } = useUser()

  useRealtime({
    enabled: Boolean(user),
    channels: [`user-${user?.id}`],
    events: ["notification.alert"],
    onData({ event, data, channel }) {
      console.log(data)
    },
  })

  return <p>Notifications {user ? "enabled" : "disabled"}</p>
}
```

## Multiple Events

Subscribe to multiple events at once:

```tsx page.tsx
"use client"

import { useRealtime } from "@/lib/realtime-client"

export default function Page() {
  useRealtime({
    events: ["chat.message", "chat.reaction", "user.joined"],
    onData({ event, data, channel }) {
      // 👇 data is automatically typed based on the event
      if (event === "chat.message") console.log("New message:", data)
      if (event === "chat.reaction") console.log("New reaction:", data)
      if (event === "user.joined") console.log("User joined:", data)
    },
  })

  return <p>Listening to multiple events</p>
}
```

## Multiple Channels

Subscribe to multiple channels at once:

```tsx page.tsx
"use client"

import { useRealtime } from "@/lib/realtime-client"

export default function Page() {
  useRealtime({
    channels: ["global", "announcements", "user-123"],
    events: ["notification.alert"],
    onData({ event, data, channel }) {
      console.log(`Message from ${channel}:`, data)
    },
  })

  return <p>Listening to multiple channels</p>
}
```

### Dynamic Channel Management

Add and remove channels dynamically:

```tsx page.tsx
"use client"

import { useState } from "react"
import { useRealtime } from "@/lib/realtime-client"

export default function Page() {
  const [channels, setChannels] = useState<string[]>(["lobby"])

  useRealtime({
    channels,
    events: ["chat.message"],
    onData({ event, data, channel }) {
      console.log(`Message from ${channel}:`, data)
    },
  })

  const joinRoom = (roomId: string) => {
    setChannels((prev) => [...prev, roomId])
  }

  const leaveRoom = (roomId: string) => {
    setChannels((prev) => prev.filter((c) => c !== roomId))
  }

  return (
    <div>
      <p>Active channels: {channels.join(", ")}</p>
      <button onClick={() => joinRoom("room-1")}>Join Room 1</button>
      <button onClick={() => joinRoom("room-2")}>Join Room 2</button>
      <button onClick={() => leaveRoom("lobby")}>Leave Lobby</button>
    </div>
  )
}
```

## Custom API Endpoint

Configure a custom realtime endpoint in the provider:

```tsx providers.tsx
"use client"

import { RealtimeProvider } from "@upstash/realtime/client"

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <RealtimeProvider
      api={{
        url: "/api/custom-realtime",
        withCredentials: true,
      }}
    >
      {children}
    </RealtimeProvider>
  )
}
```

## Use Cases

<AccordionGroup>
  <Accordion title="Live Notifications">
    Show real-time notifications to users:

    ```tsx notifications.tsx
    "use client"

    import { useRealtime } from "@/lib/realtime-client"
    import { toast } from "react-hot-toast"
    import { useUser } from "@/hooks/auth"

    export default function Notifications() {
      const { user } = useUser()

      useRealtime({
        channels: [`user-${user.id}`],
        events: ["notification.alert"],
        onData({ data }) {
          toast(data)
        },
      })

      return <p>Listening for notifications...</p>
    }
    ```

  </Accordion>

  <Accordion title="Realtime Chat">
    Build a real-time chat:

    ```tsx chat.tsx
    "use client"

    import { useState } from "react"
    import { useRealtime } from "@/lib/realtime-client"
    import z from "zod/v4"
    import type { RealtimeEvents } from "@/lib/realtime"

    type Message = z.infer<RealtimeEvents["chat"]["message"]>

    export default function Chat() {
      const [messages, setMessages] = useState<Message[]>([])

      useRealtime({
        channels: ["room-123"],
        events: ["chat.message"],
        onData({ data }) {
          setMessages((prev) => [...prev, data])
        },
      })

      return (
        <div>
          {messages.map((msg, i) => (
            <p key={i}>
              <span className="font-bold">{msg.sender}:</span> {msg.text}
            </p>
          ))}
        </div>
      )
    }
    ```

  </Accordion>

  <Accordion title="Live Dashboard">
    Update metrics in real-time:

    ```tsx dashboard.tsx
    "use client"

    import { useQuery, useQueryClient } from "@tanstack/react-query"
    import { useRealtime } from "@/lib/realtime-client"

    export default function Dashboard() {
      const queryClient = useQueryClient()

      const { data: metrics } = useQuery({
        queryKey: ["metrics"],
        queryFn: async () => {
          const res = await fetch("/api/metrics?user=user-123")
          return res.json()
        },
      })

      useRealtime({
        channels: ["user-123"],
        events: ["metrics.update"],
        onData() {
          queryClient.invalidateQueries({ queryKey: ["metrics"] })
        },
      })

      return (
        <div>
          <p>Active Users: {metrics?.users}</p>
          <p>Revenue: ${metrics?.revenue}</p>
        </div>
      )
    }
    ```

  </Accordion>

  <Accordion title="Collaborative Editing">
    Sync changes across users:

    ```tsx editor.tsx
    "use client"

    import { useState } from "react"
    import { useRealtime } from "@/lib/realtime-client"

    export default function Editor({ documentId }: { documentId: string }) {
      const [content, setContent] = useState("")

      useRealtime({
        channels: [`doc-${documentId}`],
        events: ["document.update"],
        onData({ data }) {
          setContent(data.content)
        },
      })

      return <textarea value={content} onChange={(e) => setContent(e.target.value)} />
    }
    ```

  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Channels" icon="tower-broadcast" href="/realtime/features/channels">
    Scope events to specific rooms or users
  </Card>

  <Card title="History" icon="clock-rotate-left" href="/realtime/features/history">
    Configure message retention and replay
  </Card>
</CardGroup>

# History
Source: https://upstash.com/docs/realtime/features/history

Message history allows you to retrieve past events and replay them to clients on connection. This is useful for making sure clients always have the latest state.

## Overview

All Upstash Realtime messages are automatically stored in Redis Streams. This way, messages are always delivered correctly, even after reconnects or network interruptions.

Clients can fetch past events and optionally subscribe to new events.

## Configuration

```typescript lib/realtime.ts
import { Realtime } from "@upstash/realtime"
import { redis } from "./redis"
import z from "zod/v4"

const schema = {
  chat: {
    message: z.object({
      text: z.string(),
      sender: z.string(),
    }),
  },
}

export const realtime = new Realtime({
  schema,
  redis,
  history: {
    maxLength: 100,
    expireAfterSecs: 86400,
  },
})
```

<ParamField path="maxLength" type="number" default="Infinite">
  Maximum number of messages to retain per channel. Example: `maxLength: 100` will keep
  the last 100 messages in the stream and automatically remove older messages as new ones
  are added.
</ParamField>

<ParamField path="expireAfterSecs" type="number" default="Infinite">
  How long to keep messages per channel before deleting them (in seconds). Resets every
  time a message is emitted to this channel.
</ParamField>

## Server-Side History

Retrieve and process history on the server:

```typescript route.ts
import { realtime } from "@/lib/realtime"

export const GET = async () => {
  const messages = await realtime.channel("room-123").history()

  return new Response(JSON.stringify(messages))
}
```

### History Options

<ParamField path="limit" type="number" default="1000">
  Maximum number of messages to retrieve (capped at 1000)
</ParamField>

<ParamField path="start" type="number">
  Fetch messages after this Unix timestamp (in milliseconds)
</ParamField>

<ParamField path="end" type="number">
  Fetch messages before this Unix timestamp (in milliseconds)
</ParamField>

```typescript route.ts
const messages = await realtime.channel("room-123").history({
  limit: 50,
  start: Date.now() - 86400000,
})
```

### History Response

Each history message contains:

```typescript
type HistoryMessage = {
  id: string
  event: string
  channel: string
  data: unknown
}
```

### Subscribe with History

You can automatically replay past messages when subscribing to a channel:

```typescript route.ts
await realtime.channel("room-123").subscribe({
  events: ["chat.message"],
  history: true,
  onData({ event, data, channel }) {
    console.log("Message from room-123:", data)
  },
})
```

Pass history options for more control:

```typescript route.ts
await realtime.channel("room-123").subscribe({
  events: ["chat.message"],
  history: {
    limit: 50,
    start: Date.now() - 3600000,
  },
  onData({ data }) {
    console.log("Message:", data)
  },
})
```

## Use Cases

<AccordionGroup>
  <Accordion title="Chat Application">
    Load recent messages when a user joins a room:

    <Info>We recommend keeping long chat histories in a database (e.g. Redis) and only fetching the latest messages from Upstash Realtime.</Info>

    ```tsx page.tsx
    "use client"

    import { useRealtime } from "@/lib/realtime-client"
    import { useState, useEffect } from "react"
    import z from "zod/v4"
    import type { RealtimeEvents } from "@/lib/realtime"

    type Message = z.infer<RealtimeEvents["chat"]["message"]>

    export default function ChatRoom({ roomId }: { roomId: string }) {
      const [messages, setMessages] = useState<Message[]>([])

      useEffect(() => {
        fetch(`/api/history?channel=${roomId}`)
          .then((res) => res.json())
          .then((history) => setMessages(history.map((m: any) => m.data)))
      }, [roomId])

      useRealtime({
        channels: [roomId],
        events: ["chat.message"],
        onData({ data }) {
          setMessages((prev) => [...prev, data])
        },
      })

      return (
        <div>
          {messages.map((msg, i) => (
            <div key={i}>
              <strong>{msg.sender}:</strong> {msg.text}
            </div>
          ))}
        </div>
      )
    }
    ```

  </Accordion>

  <Accordion title="Notification Center">
    Show unread notifications with history:

    ```tsx notifications.tsx
    "use client"

    import { useRealtime } from "@/lib/realtime-client"
    import { useUser } from "@/hooks/auth"
    import { useState, useEffect } from "react"
    import z from "zod/v4"
    import type { RealtimeEvents } from "@/lib/realtime"

    type Notification = z.infer<RealtimeEvents["notification"]["alert"]>

    export default function Notifications() {
      const user = useUser()
      const [notifications, setNotifications] = useState<Notification[]>([])

      useEffect(() => {
        fetch(`/api/history?channel=user-${user.id}`)
          .then((res) => res.json())
          .then((history) => {
            const unread = history.filter((m: any) => m.data.status === "unread")
            setNotifications(unread.map((m: any) => m.data))
          })
      }, [user.id])

      useRealtime({
        channels: [`user-${user.id}`],
        events: ["notification.alert"],
        onData({ data }) {
          if (data.status === "unread") {
            setNotifications((prev) => [...prev, data])
          }
        },
      })

      return (
        <div>
          {notifications.map((notif, i) => (
            <div key={i}>{notif}</div>
          ))}
        </div>
      )
    }
    ```

  </Accordion>

  <Accordion title="Live Activity Feed">
    Replay recent activity when users visit:

    ```tsx activity-feed.tsx
    "use client"

    import { useRealtime } from "@/lib/realtime-client"
    import { useTeam } from "@/hooks/team"
    import { useState, useEffect } from "react"
    import z from "zod/v4"
    import type { RealtimeEvents } from "@/lib/realtime"

    type Activity = z.infer<RealtimeEvents["activity"]["update"]>

    export default function ActivityFeed() {
      const team = useTeam()
      const [activities, setActivities] = useState<Activity[]>([])

      useEffect(() => {
        fetch(`/api/history?channel=team-${team.id}&limit=100`)
          .then((res) => res.json())
          .then((history) => setActivities(history.map((m: any) => m.data)))
      }, [team.id])

      useRealtime({
        channels: [`team-${team.id}`],
        events: ["activity.update"],
        onData({ data }) {
          setActivities((prev) => [data, ...prev])
        },
      })

      return (
        <div>
          {activities.map((activity, i) => (
            <div key={i}>{activity.message}</div>
          ))}
        </div>
      )
    }
    ```

  </Accordion>
</AccordionGroup>

## How It Works

1. When you emit an event, it's stored in a Redis Stream with a unique stream ID
2. The stream is trimmed to `maxLength` if configured
3. The stream expires after `expireAfterSecs` if configured
4. History can be fetched via `channel.history()` on the server
5. History is replayed in chronological order (oldest to newest)
6. New events continue streaming right after history replay, no messages lost

## Performance Considerations

Upstash Realtime can handle extremely large histories without problems. The bottleneck is the client who needs to handle all replayed events.

At that point you should probably consider using a database like Redis or Postgres to fetch the history once, then stream new events to the client with Upstash Realtime.

<AccordionGroup>
  <Accordion title="Limit History Length">
    For high-volume channels, limit history to prevent large initial payloads.

    ```typescript lib/realtime.ts
    export const realtime = new Realtime({
      schema,
      redis,
      history: {
        maxLength: 1000,
      },
    })
    ```

  </Accordion>

  <Accordion title="Set Expiration">
    Expire old messages to reduce storage:

    ```typescript lib/realtime.ts
    export const realtime = new Realtime({
      schema,
      redis,
      history: {
        expireAfterSecs: 3600,
      },
    })
    ```

  </Accordion>

</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Server-Side Usage" icon="server" href="/realtime/features/server-side">
    Stream history and subscribe to events on the server
  </Card>

  <Card title="Channels" icon="tower-broadcast" href="/realtime/features/channels">
    Scope history to specific rooms or users
  </Card>
</CardGroup>

# Authentication
Source: https://upstash.com/docs/realtime/features/middleware

Protect your realtime endpoints with custom auth logic.

## Basic Middleware

```typescript api/realtime/route.ts
import { handle } from "@upstash/realtime"
import { realtime } from "@/lib/realtime"
import { currentUser } from "@/auth"

export const GET = handle({
  realtime,
  middleware: async ({ request, channels }) => {
    const user = await currentUser(request)

    if (!user) {
      return new Response("Unauthorized", { status: 401 })
    }
  },
})
```

## Middleware API

The middleware function receives:

<ParamField path="request" type="Request">
  The incoming HTTP Request object
</ParamField>

<ParamField path="channels" type="string[]" default="['default']">
  The channels a user is attempting to connect to
</ParamField>

<ResponseField name="return">
  * Return `undefined` or nothing to allow the connection
  * Return a `Response` object to block the connection with a custom error
</ResponseField>

## Authentication Patterns

<AccordionGroup>
  <Accordion title="User-Based Auth">
    Verify users can access specific channels:

    ```typescript api/realtime/route.ts
    export const GET = handle({
      realtime,
      middleware: async ({ request, channels }) => {
        const user = await currentUser(request)

        for (const channel of channels) {
          if (channel === "default") {
            continue
          }

          if (!channel.startsWith(user.id)) {
            return new Response("You can only access your own channels", { status: 403 })
          }
        }
      },
    })
    ```

  </Accordion>

  <Accordion title="Session-Based Auth">
    Verify user sessions before allowing connections:

    ```typescript api/realtime/route.ts
    import { getSession } from "@/auth"

    export const GET = handle({
      realtime,
      middleware: async ({ request }) => {
        const session = await getSession(request)

        if (!session?.user) {
          return new Response("Please sign in", { status: 401 })
        }
      },
    })
    ```

  </Accordion>

  <Accordion title="Role-Based Access">
    Control access based on user roles:

    ```typescript api/realtime/route.ts
    export const GET = handle({
      realtime,
      middleware: async ({ request, channels }) => {
        const user = await currentUser(request)

        for (const channel of channels) {
          if (channel === "default") {
            continue
          }

          if (channel.startsWith("admin-") && user.role !== "admin") {
            return new Response("Admin access required", { status: 403 })
          }

          if (channel.startsWith("team-")) {
            const teamId = channel.replace("team-", "")
            const isMember = await checkTeamMembership(user.id, teamId)

            if (!isMember) {
              return new Response("Not a team member", { status: 403 })
            }
          }
        }
      },
    })
    ```

  </Accordion>
</AccordionGroup>

# Server-Side Usage
Source: https://upstash.com/docs/realtime/features/server-side

Use Upstash Realtime on the server to emit events, subscribe to channels, and retrieve message history.

## Emit Events

Emit events from any server context:

```typescript route.ts
import { realtime } from "@/lib/realtime"

export const POST = async () => {
  await realtime.emit("notification.alert", "hello world!")

  return new Response("OK")
}
```

Emit to specific channels:

```typescript route.ts
import { realtime } from "@/lib/realtime"

export const POST = async () => {
  const channel = realtime.channel("user-123")
  await channel.emit("notification.alert", "hello world!")

  return new Response("OK")
}
```

## Subscribe to Events

Subscribe to events on a channel:

```typescript route.ts
import { realtime } from "@/lib/realtime"

const unsubscribe = await realtime.channel("notifications").subscribe({
  events: ["notification.alert"],
  onData({ event, data, channel }) {
    console.log("New notification:", data)
  },
})
```

Subscribe to multiple events:

```typescript route.ts
import { realtime } from "@/lib/realtime"

const unsubscribe = await realtime.channel("room-123").subscribe({
  events: ["chat.message", "user.joined", "user.left"],
  onData({ event, data, channel }) {
    // 👇 data is automatically typed based on the event
    if (event === "chat.message") console.log("New message:", data)
    if (event === "user.joined") console.log("User joined:", data)
    if (event === "user.left") console.log("User left:", data)
  },
})
```

### Unsubscribe

Clean up subscriptions when done:

```typescript route.ts
import { realtime } from "@/lib/realtime"

const channel = realtime.channel("room-123")

const unsubscribe = await channel.subscribe({
  events: ["chat.message"],
  onData({ data }) {
    console.log("Message:", data)
  },
})

unsubscribe()
// or: channel.unsubscribe()
```

## Retrieve History

Fetch past messages from a channel:

```typescript route.ts
import { realtime } from "@/lib/realtime"

export const GET = async () => {
  const messages = await realtime.channel("room-123").history()

  return new Response(JSON.stringify(messages))
}
```

### History Options

<ParamField path="limit" type="number" default="1000">
  Maximum number of messages to retrieve (capped at 1000)
</ParamField>

<ParamField path="start" type="number">
  Fetch messages after this Unix timestamp (in milliseconds)
</ParamField>

<ParamField path="end" type="number">
  Fetch messages before this Unix timestamp (in milliseconds)
</ParamField>

```typescript route.ts
const messages = await realtime.channel("room-123").history({
  limit: 100,
  start: Date.now() - 86400000,
})
```

### History Response

Each history message contains:

```typescript
type HistoryMessage = {
  id: string
  event: string
  channel: string
  data: unknown
}
```

## Subscribe with History

Replay past messages and continue subscribing to new ones:

```typescript route.ts
import { realtime } from "@/lib/realtime"

const channel = realtime.channel("room-123")

await channel.subscribe({
  events: ["chat.message"],
  history: true,
  onData({ event, data, channel }) {
    console.log("Message:", data)
  },
})
```

Pass history options:

```typescript route.ts
await channel.subscribe({
  events: ["chat.message"],
  history: {
    limit: 50,
    start: Date.now() - 3600000,
  },
  onData({ data }) {
    console.log("Message:", data)
  },
})
```

This pattern:

1. Fetches messages matching the history criteria
2. Replays them in chronological order
3. Continues to listen for new messages

## Use Cases

<AccordionGroup>
  <Accordion title="Background Jobs">
    Stream progress updates from long-running tasks:

    ```typescript app/api/job/route.ts
    import { realtime } from "@/lib/realtime"

    export const POST = async (req: Request) => {
      const { jobId } = await req.json()
      const channel = realtime.channel(jobId)

      await channel.emit("job.started", { progress: 0 })

      for (let i = 0; i <= 100; i += 10) {
        await processChunk()
        await channel.emit("job.progress", { progress: i })
      }

      await channel.emit("job.completed", { progress: 100 })

      return new Response("OK")
    }
    ```

  </Accordion>

  <Accordion title="Event Processing">
    Process events with server-side logic:

    ```typescript route.ts
    import { realtime } from "@/lib/realtime"
    import { sendEmail } from "@/lib/email"

    await realtime.channel("notifications").subscribe({
      events: ["notification.alert"],
      onData: async ({ data }) => {
        if (data.priority === "high") {
          await sendEmail({
            to: data.userId,
            subject: "Urgent Notification",
            body: data.message,
          })
        }
      },
    })
    ```

  </Accordion>

  <Accordion title="Multi-Channel Broadcasting">
    Emit events to multiple channels:

    ```typescript route.ts
    import { realtime } from "@/lib/realtime"

    export const POST = async (req: Request) => {
      const { teamIds, message } = await req.json()

      await Promise.all(
        teamIds.map((teamId: string) =>
          realtime.channel(`team-${teamId}`).emit("announcement", message)
        )
      )

      return new Response("Broadcast sent")
    }
    ```

  </Accordion>

  <Accordion title="Webhook Processing">
    Forward webhook events to realtime channels:

    ```typescript app/api/webhook/route.ts
    import { realtime } from "@/lib/realtime"

    export const POST = async (req: Request) => {
      const payload = await req.json()

      const channel = realtime.channel(`user-${payload.userId}`)
      await channel.emit("webhook.received", payload)

      return new Response("OK")
    }
    ```

  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="History" icon="clock-rotate-left" href="/realtime/features/history">
    Configure message retention and expiration
  </Card>

  <Card title="Channels" icon="tower-broadcast" href="/realtime/features/channels">
    Scope events to specific rooms or users
  </Card>
</CardGroup>

# Deployment
Source: https://upstash.com/docs/realtime/features/serverless

<Note>
  Deploy Upstash Realtime to providers that bill **based on active CPU time**. Great places to
  deploy are
  * Vercel with Fluid Compute enabled
  * Cloudflare
  * Railway
  * A personal VPS
  * any other service that does not bill based on connection duration.
</Note>

## Deploying to Vercel

To deploy Upstash Realtime to Vercel, [enable Fluid Compute](https://vercel.com/docs/fluid-compute#enable-for-entire-project) for your project. For new projects, this is enabled by default.

Fluid Compute allows for less cold-starts, has much higher function timeouts compared to serverless functions, and most importantly **only bills for active CPU time**.

That way, you're only billed for actual message processing time, not connection duration.

  <img />

## Optional: Configure Max Duration

You can configure the maximum duration for your realtime connections:

```typescript lib/realtime.ts
import { Realtime } from "@upstash/realtime"
import { redis } from "./redis"

export const realtime = new Realtime({
  schema,
  redis,
  maxDurationSecs: 300,
})
```

The default is 300 seconds (5 minutes), which works well with Vercel's Fluid Compute. After this interval, the client will automatically reconnect. Redis auto-replays all messages sent during reconnect.

## Billing Example

Traditional serverless connection billing:

```plaintext Serverless Billing
Connection duration: 5 minutes
Billing: 5 minutes = $$$
```

Upstash Realtime with fluid compute:

```plaintext Fluid Compute Billing
Connection duration: 5 minutes
Active processing: 2 seconds
Billing: 2 seconds x CPU cost = $
```

## Automatic Reconnection

The client automatically reconnects before your function timeout:

```tsx page.tsx
"use client"

import { useRealtime } from "@/lib/realtime-client"

export default function Component() {
  const { status } = useRealtime({
    events: ["notification.alert"],
    onData({ event, data, channel }) {},
  })

  return <p>Status: {status}</p>
}
```

## Message Delivery Guarantee

Upstash Realtime is powered by Redis Streams, so no message is ever delivered twice or gets lost. Every message is guaranteed to be delivered exactly once.

1. Client establishes connection and subscribes to stream
2. Client initiates reconnection before function timeout (default every 5 mins)
3. Redis auto-replays all messages sent during reconnect

# Pricing
Source: https://upstash.com/docs/realtime/overall/pricing

**Upstash Realtime is designed to be extremely cost-efficient.** With minimal Redis commands per operation and smart connection management, you can build real-time features at scale without worrying about costs.

Upstash Realtime is built on Redis Streams and Pub/Sub. Every operation translates to one or more Redis commands, detailed below.

## Command Overview

### Client-Side Operations

When using [`useRealtime`](/docs/realtime/features/client-side#basic-usage) in your React components:

| Operation | Commands | Count |
| --------- | -------- | ----- |
| Initial connection | SUBSCRIBE, XRANGE | 2 |
| Reconnection every 300 seconds | UNSUBSCRIBE, XRANGE, SUBSCRIBE | 3 |
| Ping to keep connection alive every 60 seconds | PUBLISH | 1 |

### Server-Side Operations

When using the [server-side API](/docs/realtime/features/server-side):

| Operation | Commands | Count |
| --------- | -------- | ----- |
| [Emit event](/docs/realtime/features/server-side#emit-events) | PUBLISH, XADD | 2 |
| Emit with [`expireAfterSecs`](/docs/realtime/features/history#param-expire-after-secs) | PUBLISH, XADD, EXPIRE | 3 |
| [Read history](/docs/realtime/features/history#server-side-history) | XRANGE | 1 |

## Next Steps

<CardGroup cols={2}>
  <Card title="Client-Side Usage" icon="browser" href="/realtime/features/client-side">
    Learn how to use the useRealtime hook in React
  </Card>
  <Card title="History" icon="clock-rotate-left" href="/realtime/features/history">
    Learn how to read event history
  </Card>
</CardGroup>

# Quickstart
Source: https://upstash.com/docs/realtime/overall/quickstart

Upstash Realtime is the easiest way to add realtime features to any Next.js project.

  <img
    src="https://raw.githubusercontent.com/upstash/realtime/refs/heads/main/public/thumbnail.png"
    alt="Upstash Realtime"
  />

## Why Upstash Realtime?

* 🧨 Clean APIs & first-class TypeScript support
* ⚡ Extremely fast, zero dependencies, 2.6kB gzipped
* 💻 Deploy anywhere: Vercel, Netlify, etc.
* 💎 100% type-safe with zod 4 or zod mini
* ⏱️ Built-in message histories
* 🔌 Automatic connection management w/ delivery guarantee
* 🔋 Built-in middleware and authentication helpers
* 📶 100% HTTP-based: Redis streams & SSE

***

## Quickstart

### 1. Installation

<CodeGroup>
```bash npm
npm install @upstash/realtime @upstash/redis zod
```

```bash yarn
yarn add @upstash/realtime @upstash/redis zod
```

```bash pnpm
pnpm add @upstash/realtime @upstash/redis zod
```

```bash bun
bun install @upstash/realtime @upstash/redis zod
```

</CodeGroup>

### 2. Configure Upstash Redis

Upstash Realtime is powered by Redis Streams. Grab your credentials from the [Upstash Console](https://console.upstash.com).

  <img width="100%" />

Add them to your environment variables:

```bash title=".env"
UPSTASH_REDIS_REST_URL=https://striking-osprey-20681.upstash.io
UPSTASH_REDIS_REST_TOKEN=AVDJAAIjcDEyZ...
```

And lastly, create a Redis instance:

```typescript title="lib/redis.ts"
import { Redis } from "@upstash/redis"

export const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL,
  token: process.env.UPSTASH_REDIS_REST_TOKEN,
})
```

### 3. Define Event Schema

Define the structure of realtime events in your app:

```typescript title="lib/realtime.ts"
import { Realtime, InferRealtimeEvents } from "@upstash/realtime"
import { redis } from "./redis"
import z from "zod/v4"

const schema = {
  notification: {
    alert: z.string(),
  },
}

export const realtime = new Realtime({ schema, redis })
export type RealtimeEvents = InferRealtimeEvents<typeof realtime>
```

### 4. Create Realtime Route Handler

Create a route handler at `api/realtime/route.ts`:

```typescript title="app/api/realtime/route.ts"
import { handle } from "@upstash/realtime"
import { realtime } from "@/lib/realtime"

export const GET = handle({ realtime })
```

### 5. Add the Provider

Wrap your application in `RealtimeProvider`:

```tsx title="app/providers.tsx"
"use client"

import { RealtimeProvider } from "@upstash/realtime/client"

export function Providers({ children }: { children: React.ReactNode }) {
  return <RealtimeProvider>{children}</RealtimeProvider>
}
```

```tsx title="app/layout.tsx"
import { Providers } from "./providers"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}
```

### 6. Create Typed Client Hook

Create a typed `useRealtime` hook for your client components:

```typescript title="lib/realtime-client.ts"
"use client"

import { createRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "./realtime"

export const { useRealtime } = createRealtime<RealtimeEvents>()
```

### 7. Emit Events

From any server component, server action, or API route:

```typescript title="app/api/notify/route.ts"
import { realtime } from "@/lib/realtime"

export const POST = async () => {
  await realtime.emit("notification.alert", "hello world!")

  return new Response("OK")
}
```

### 8. Subscribe to Events

In any client component:

```tsx title="app/components/notifications.tsx"
"use client"

import { useRealtime } from "@/lib/realtime-client"

export default function Notifications() {
  useRealtime({
    events: ["notification.alert"],
    onData({ event, data, channel }) {
      console.log(`Received ${event}:`, data)
    },
  })

  return <p>Listening for events...</p>
}
```

That's it! Your app is now listening for realtime events with full type safety. 🎉

### 9. Realtime Dashboard

For debugging or monitoring purposes, you can use Realtime Dashboard in console.

  <img />

## Next Steps

<CardGroup cols={2}>
  <Card title="Client-Side Usage" icon="react" href="/realtime/features/client-side">
    Complete guide to the useRealtime hook
  </Card>

  <Card title="Server-Side Usage" icon="server" href="/realtime/features/server-side">
    Subscribe to events and stream updates on the server
  </Card>

  <Card title="Channels" icon="tower-broadcast" href="/realtime/features/channels">
    Scope events to specific rooms or channels
  </Card>

  <Card title="History" icon="clock-rotate-left" href="/realtime/features/history">
    Fetch and replay past messages
  </Card>
</CardGroup>

# BITCOUNT
Source: https://upstash.com/docs/redis/commands/bitmap/bitcount

Use `BITCOUNT` to count the bits set to 1 in the string stored at a key.

Without a range the whole value is counted. `<start>` and `<end>` restrict the count to a part of the value and are interpreted as byte offsets by default, or as bit offsets when `BIT` is given. Both ends are inclusive and may be negative to count backwards from the end of the value, where `-1` is the last byte or bit. A missing key is treated as an empty string and returns `0`.

`BITCOUNT` is the usual way to read a bitmap built with [`SETBIT`](/docs/redis/commands/bitmap/setbit), for example to count how many users were active on a given day when each user has a fixed bit position.

## Syntax

```redis
BITCOUNT <key> [<start> <end> [BYTE | BIT]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<start> <end> [BYTE \| BIT]` | No | No | Range to count. Offsets are byte-based unless `BIT` is given; negative offsets count from the end. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
BITCOUNT my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const bits = await redis.bitcount(key);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.bitcount("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.bitcount("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.bitCount("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.bitcount("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.BitCount(context.Background(), "my-key", nil).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.bitcount("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.bitcount("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# BITFIELD
Source: https://upstash.com/docs/redis/commands/bitmap/bitfield

Use `BITFIELD` to treat a string as an array of packed integers and run several operations on it in a single atomic call.

Every operation names an encoding and a bit offset. The encoding is `u<bits>` for unsigned integers (up to 63 bits) or `i<bits>` for signed integers (up to 64 bits). The offset is counted in bits from the start of the value or, when prefixed with `#`, in units of the encoding width, so `#2` with `u8` addresses the third 8-bit field. The string grows automatically with zero bits when an operation addresses an offset past its current end.

`GET` reads a field, `SET` writes one and returns its previous value, and `INCRBY` adds a possibly negative increment and returns the new value. `OVERFLOW` sets how the `SET` and `INCRBY` operations that follow it behave when a value does not fit the encoding: `WRAP` wraps around like modular arithmetic and is the default, `SAT` saturates at the minimum or maximum of the encoding, and `FAIL` leaves the field unchanged and returns null for that operation. The reply is an array with one entry per operation, in the order the operations were given.

Packing many small counters into a single key this way saves memory and keeps the whole update atomic, which makes it a good fit for rate limiters and compact per-user counters.

## Syntax

```redis
BITFIELD <key>
  [GET <encoding> <offset> |
    [OVERFLOW WRAP | SAT | FAIL]
    (SET <encoding> <offset> <value> |
      INCRBY <encoding> <offset> <increment>)
    [GET <encoding> <offset> |
      [OVERFLOW WRAP | SAT | FAIL]
      (SET <encoding> <offset> <value> |
        INCRBY <encoding> <offset> <increment>)
      ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `(GET <encoding> <offset> \| [OVERFLOW WRAP \| SAT \| FAIL] (SET <encoding> <offset> <value> \| INCRBY <encoding> <offset> <increment>))` | No | Yes | An operation on a field of `<encoding>` (`u<bits>` unsigned up to 63 bits, or `i<bits>` signed up to 64 bits) at `<offset>` bits, or at `#<n>` to address the n-th field of that width: `GET` reads it, `SET` writes it and returns the previous value, and `INCRBY` adds an increment and returns the new value. `OVERFLOW` sets how the `SET` and `INCRBY` operations after it handle a value that does not fit: `WRAP` wraps around (the default), `SAT` saturates at the encoding's limits, and `FAIL` leaves the field unchanged and returns null. Repeat to run several operations in one atomic call. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integer or null replies, one per subcommand |
| RESP3 | Array of integer or null replies, one per subcommand |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
BITFIELD my-key GET u8 0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.bitfield("my-key").get("u8", 0).exec();
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.bitfield("my-key").get("u8", 0).execute()
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.bitfield("my-key", "GET", "u8", "0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.bitField("my-key", [{ operation: "GET", encoding: "u8", offset: 0 }]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.bitfield("my-key").get("u8", 0).execute()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.BitField(context.Background(), "my-key", "GET", "u8", 0).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.bitfield("my-key", "GET", "u8", "0");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("BITFIELD");
    command.arg("my-key");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# BITFIELD_RO
Source: https://upstash.com/docs/redis/commands/bitmap/bitfield-ro

Use `BITFIELD_RO` to read one or more bitfield values without modifying the key.

It is the read-only form of [`BITFIELD`](/docs/redis/commands/bitmap/bitfield) and accepts `GET` operations only, so it is safe to run on replicas and from read-only scripts. Each `GET` names an encoding, `u<bits>` for unsigned or `i<bits>` for signed integers, and a bit offset that can be written as `#<n>` to address the n-th field of that width. The reply holds one integer per `GET`, and any part of a field that lies past the end of the stored string reads as zero.

## Syntax

```redis
BITFIELD_RO <key> [GET <encoding> <offset> [GET <encoding> <offset> ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `GET <encoding> <offset>` | No | Yes | Read the value at `<offset>` using `<encoding>`, such as `u8` or `i16`. Repeat to read several fields. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integer or null replies, one per subcommand |
| RESP3 | Array of integer or null replies, one per subcommand |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
BITFIELD_RO my-key GET u8 0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.bitfield_ro("my-key").get("u8", 0).execute()
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.bitfield_ro("my-key", "GET", "u8", "0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.bitFieldRo("my-key", [{ encoding: "u8", offset: 0 }]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.bitfield_ro("my-key", "u8", 0)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.BitFieldRO(context.Background(), "my-key", "GET", "u8", 0).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.bitfieldReadonly("my-key", "GET", "u8", "0");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("BITFIELD_RO");
    command.arg("my-key");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# BITOP
Source: https://upstash.com/docs/redis/commands/bitmap/bitop

Use `BITOP` to combine several strings with a bitwise operation and store the result in another key.

Source strings are combined bit by bit, and shorter ones are treated as if they were padded with zero bits up to the length of the longest input, so the destination always ends up as long as the longest source. A missing key counts as an empty string, and if the result is empty the destination key is deleted. The reply is the length of the stored value in bytes.

`AND`, `OR`, and `XOR` accept any number of source keys and `NOT` accepts exactly one. The remaining operators compare the first key with the rest: `DIFF` keeps the bits set in the first key and in none of the others, `DIFF1` keeps the bits set in at least one of the other keys but not in the first, `ANDOR` keeps the bits set in the first key and in at least one of the others, and `ONE` keeps the bits set in exactly one of the source keys. `DIFF`, `DIFF1`, and `ANDOR` each require at least two source keys.

This is how bitmaps are used as sets: with one bit per user, `AND` gives users present in every bitmap and `OR` gives users present in any of them, and [`BITCOUNT`](/docs/redis/commands/bitmap/bitcount) then turns the result into a number.

## Syntax

```redis
BITOP (AND | OR | XOR | NOT | DIFF | DIFF1 | ANDOR | ONE) <destkey> <key> [<key> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `(AND \| OR \| XOR \| NOT \| DIFF \| DIFF1 \| ANDOR \| ONE)` | Yes | No | The bitwise operation to apply. `AND`, `OR`, and `XOR` combine any number of source keys and `NOT` inverts exactly one. The rest compare the first key with the others: `DIFF` keeps bits set in the first key and in none of the others, `DIFF1` keeps bits set in at least one of the others but not in the first, `ANDOR` keeps bits set in the first key and in at least one of the others, and `ONE` keeps bits set in exactly one source key. |
| `<destkey>` | Yes | No | Redis key used as destkey. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
BITOP AND destination-key source-key-1 source-key-2
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

// AND operation
await redis.bitop("and", "destKey", "sourceKey1", "sourceKey2");

// OR operation
await redis.bitop("or", "destKey", "sourceKey1", "sourceKey2");

// XOR operation
await redis.bitop("xor", "destKey", "sourceKey1", "sourceKey2");

// NOT operation (only accepts one source key)
await redis.bitop("not", "destKey", "sourceKey");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.bitop("AND", "destination-key", "source-key-1", "source-key-2")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.bitop("AND", "destination-key", "source-key-1", "source-key-2");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.bitOp("AND", "destination-key", ["source-key-1", "source-key-2"]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.bitop("AND", "destination-key", "source-key-1", "source-key-2")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.BitOpAnd(context.Background(), "destination-key", "source-key-1", "source-key-2").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.bitop(redis.clients.jedis.args.BitOP.AND, "destination-key", "source-key-1", "source-key-2");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.bit_and("destination-key", &["source-key-1", "source-key-2"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# BITPOS
Source: https://upstash.com/docs/redis/commands/bitmap/bitpos

Use `BITPOS` to find the position of the first bit set to `0` or `1` in a string.

The whole value is searched unless `<start>` and `<end>` are given, and those are byte offsets by default or bit offsets when `BIT` is given. Both ends are inclusive and may be negative to count backwards from the end of the value. The reply is always an absolute bit position counted from the start of the string, or `-1` when no matching bit is found.

One edge case is worth remembering: when you look for a `0` in a string of all ones and give no explicit end, the reply is the position of the first bit past the end of the string, because the value is treated as if it were followed by an infinite run of zero bits. Bounding the search with an explicit range returns `-1` in the same situation.

## Syntax

```redis
BITPOS <key> <bit> [<start> [<end> [BYTE | BIT]]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<bit>` | Yes | No | Bit value to look for: `0` or `1`. |
| `<start> [<end> [BYTE \| BIT]]` | No | No | Range to search. Offsets are byte-based unless `BIT` is given; negative offsets count from the end. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: the position of the first matching bit, or `-1` |
| RESP3 | Integer: the position of the first matching bit, or `-1` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
BITPOS my-key 1
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.bitpos("key", 1);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.bitpos("my-key", 1)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.bitpos("my-key", "1");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.bitPos("my-key", 1);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.bitpos("my-key", 1)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.BitPos(context.Background(), "my-key", 1).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.bitpos("my-key", true);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("BITPOS");
    command.arg("my-key");
    command.arg("1");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GETBIT
Source: https://upstash.com/docs/redis/commands/bitmap/getbit

Use `GETBIT` to read a single bit of the string stored at a key.

The offset is counted in bits from the start of the value, so offset `0` is the most significant bit of the first byte. When the key does not exist, or the offset lies past the end of the stored string, the bit reads as `0` rather than producing an error.

## Syntax

```redis
GETBIT <key> <offset>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<offset>` | Yes | No | Zero-based bit offset to read. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: the bit at the offset, `0` or `1` |
| RESP3 | Integer: the bit at the offset, `0` or `1` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GETBIT my-key 0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const bit = await redis.getbit(key, 4);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.getbit("my-key", 0)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.getbit("my-key", "0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.getBit("my-key", 0);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.getbit("my-key", 0)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.GetBit(context.Background(), "my-key", 0).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.getbit("my-key", 0);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.getbit("my-key", 0)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# Bitmap commands
Source: https://upstash.com/docs/redis/commands/bitmap/overview

<CardGroup cols={2}>
<Card title="BITCOUNT" href="/redis/commands/bitmap/bitcount">Count set bits in a string</Card>
<Card title="BITFIELD" href="/redis/commands/bitmap/bitfield">Perform arbitrary bitfield operations</Card>
<Card title="BITFIELD_RO" href="/redis/commands/bitmap/bitfield-ro">Read-only bitfield operations</Card>
<Card title="BITOP" href="/redis/commands/bitmap/bitop">Perform bitwise operations between strings</Card>
<Card title="BITPOS" href="/redis/commands/bitmap/bitpos">Find first bit set or clear in a string</Card>
<Card title="GETBIT" href="/redis/commands/bitmap/getbit">Get the bit value at offset</Card>
<Card title="SETBIT" href="/redis/commands/bitmap/setbit">Set or clear the bit at offset</Card>
</CardGroup>

# SETBIT
Source: https://upstash.com/docs/redis/commands/bitmap/setbit

Use `SETBIT` to set a single bit of the string stored at a key to `0` or `1`.

The offset is counted in bits from the start of the value and the reply is the bit's previous value. If the key does not exist, or the offset lies past the end of the current value, the string is first extended with zero bits, so writing to a large offset allocates every byte up to it. Keep offsets dense, for example by mapping each user to a small sequential id.

Bitmaps built this way are read back with [`GETBIT`](/docs/redis/commands/bitmap/getbit), counted with [`BITCOUNT`](/docs/redis/commands/bitmap/bitcount), and combined with [`BITOP`](/docs/redis/commands/bitmap/bitop).

## Syntax

```redis
SETBIT <key> <offset> <value>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<offset>` | Yes | No | Zero-based bit offset to write. |
| `<value>` | Yes | No | Bit value to write: `0` or `1`. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: the previous bit at the offset, `0` or `1` |
| RESP3 | Integer: the previous bit at the offset, `0` or `1` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SETBIT my-key 0 1
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const originalBit = await redis.setbit(key, 4, 1);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.setbit("my-key", 0, 1)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.setbit("my-key", "0", "1");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.setBit("my-key", 0, 1);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.setbit("my-key", 0, 1)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SetBit(context.Background(), "my-key", 0, 1).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.setbit("my-key", 0, true);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.setbit("my-key", 0, true)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# AUTH
Source: https://upstash.com/docs/redis/commands/connection/auth

Use `AUTH` to authenticate the current connection, with a password alone or with a username and password when ACL users are configured.

Until the connection is authenticated the server rejects other commands with an error. Client libraries usually send `AUTH` for you as part of connecting when the credentials are part of the connection string, so applications rarely call it directly. The password is sent to the server on every connection, which is why Upstash endpoints use TLS.

## Syntax

```redis
AUTH [<username>] <password>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<username>` | No | No | Username to authenticate as. |
| `<password>` | Yes | No | Password to authenticate with. |

## Important points

* This is a connection-oriented command and is available over native Redis TCP, not the stateless REST endpoint.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
AUTH password
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.auth("password");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.auth({ password: "password" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.auth("password")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Do(context.Background(), "AUTH", "password").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.auth("password");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("AUTH");
    command.arg("password");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# CLIENT GETNAME
Source: https://upstash.com/docs/redis/commands/connection/client-getname

Use `CLIENT GETNAME` to read the name assigned to the current connection with [`CLIENT SETNAME`](/docs/redis/commands/connection/client-setname).

Connections start without a name and the reply is null until one is set. The name belongs to a single connection and is lost when it closes; it exists to make connections recognizable in [`CLIENT LIST`](/docs/redis/commands/connection/client-list) output when you are debugging which part of an application is doing what.

## Syntax

```redis
CLIENT GETNAME
```

## Arguments

This command takes no arguments.

## Important points

* This is a connection-oriented command and is available over native Redis TCP, not the stateless REST endpoint.
* This command can expose administrative information or make a broad destructive change. Restrict it to trusted code paths.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array or Bulk string |
| RESP3 | Null or Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
CLIENT GETNAME
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.client("GETNAME");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.clientGetName();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.client_getname()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ClientGetName(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.clientGetname();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.client_getname()?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# CLIENT ID
Source: https://upstash.com/docs/redis/commands/connection/client-id

Use `CLIENT ID` to get the unique identifier the server assigned to the current connection.

IDs are integers that never repeat and always increase, so a larger ID means a connection that was established later. The ID identifies this connection in [`CLIENT LIST`](/docs/redis/commands/connection/client-list) output, which makes it useful when correlating application logs with server-side connection state.

## Syntax

```redis
CLIENT ID
```

## Arguments

This command takes no arguments.

## Important points

* This is a connection-oriented command and is available over native Redis TCP, not the stateless REST endpoint.
* This command can expose administrative information or make a broad destructive change. Restrict it to trusted code paths.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
CLIENT ID
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.client("ID");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.clientId();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.client_id()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ClientID(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.clientId();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.client_id()?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# CLIENT INFO
Source: https://upstash.com/docs/redis/commands/connection/client-info

Use `CLIENT INFO` to get a line of statistics about the connection issuing the command.

The reply is a single line of space-separated `field=value` pairs describing the connection: its id, name, address, age, idle time, the number of commands it has run, and the last command it executed. It reports the same fields as one line of [`CLIENT LIST`](/docs/redis/commands/connection/client-list), limited to your own connection, which makes it a cheap way to confirm what the server thinks of the connection you are on. Parse it defensively, since fields can be added over time.

## Syntax

```redis
CLIENT INFO
```

## Arguments

This command takes no arguments.

## Important points

* This is a connection-oriented command and is available over native Redis TCP, not the stateless REST endpoint.
* This command can expose administrative information or make a broad destructive change. Restrict it to trusted code paths.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string |
| RESP3 | Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
CLIENT INFO
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.client("INFO");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.clientInfo();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.client_info()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ClientInfo(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.clientInfo();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("CLIENT");
    command.arg("INFO");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# CLIENT LIST
Source: https://upstash.com/docs/redis/commands/connection/client-list

Use `CLIENT LIST` to get one line of statistics for every client connection to the server.

Each line holds space-separated `field=value` pairs with the connection's id, name, address, age, idle time, protocol version, and last command, so the reply gives a snapshot of who is connected and what they are doing. It is the usual starting point for tracking down connection leaks and idle connections. The reply grows with the number of clients, so avoid calling it on a hot path, and parse it defensively because fields can be added over time.

## Syntax

```redis
CLIENT LIST
```

## Arguments

This command takes no arguments.

## Important points

* This is a connection-oriented command and is available over native Redis TCP, not the stateless REST endpoint.
* This command can expose administrative information or make a broad destructive change. Restrict it to trusted code paths.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string |
| RESP3 | Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
CLIENT LIST
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.client("LIST");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.clientList();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.client_list()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ClientList(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.clientList();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("CLIENT");
    command.arg("LIST");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# CLIENT SETINFO
Source: https://upstash.com/docs/redis/commands/connection/client-setinfo

Use `CLIENT SETINFO` to attach library identification to the current connection.

`LIB-NAME` records the name of the client library and `LIB-VER` its version. Both values then appear in [`CLIENT INFO`](/docs/redis/commands/connection/client-info) and [`CLIENT LIST`](/docs/redis/commands/connection/client-list) output, which makes it possible to tell which application or SDK version owns a connection. Most client libraries send this during the handshake, so applications rarely call it themselves.

## Syntax

```redis
CLIENT SETINFO <LIB-NAME | LIB-VER> <value>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `LIB-NAME \| LIB-VER` | Yes | No | Attribute to update: the client library name or version. |
| `value` | Yes | No | Value to store for the selected client attribute. |

## Important points

* This is a connection-oriented command and is available over native Redis TCP, not the stateless REST endpoint.
* This command can expose administrative information or make a broad destructive change. Restrict it to trusted code paths.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
CLIENT SETINFO LIB-NAME my-client
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("CLIENT", "SETINFO", "LIB-NAME", "my-client");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sendCommand(["CLIENT", "SETINFO", "LIB-NAME", "my-client"]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.client_setinfo("LIB-NAME", "my-client")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Do(context.Background(), "CLIENT", "SETINFO", "LIB-NAME", "my-client").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.clientSetInfo(redis.clients.jedis.args.ClientAttributeOption.LIB_NAME, "my-client");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("CLIENT");
    command.arg("SETINFO");
    command.arg("LIB-NAME");
    command.arg("my-client");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# CLIENT SETNAME
Source: https://upstash.com/docs/redis/commands/connection/client-setname

Use `CLIENT SETNAME` to label the current connection with a name of your choice.

The name shows up in [`CLIENT LIST`](/docs/redis/commands/connection/client-list) and [`CLIENT INFO`](/docs/redis/commands/connection/client-info) output, which is handy when several components of an application share one database and you want to tell their connections apart. The name may not contain spaces or newlines, each call replaces the previous name, and passing an empty string clears it. It lives only as long as the connection.

## Syntax

```redis
CLIENT SETNAME <connection-name>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `connection-name` | Yes | No | Name to associate with this TCP connection. |

## Important points

* This is a connection-oriented command and is available over native Redis TCP, not the stateless REST endpoint.
* This command can expose administrative information or make a broad destructive change. Restrict it to trusted code paths.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
CLIENT SETNAME worker-1
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.client("SETNAME", "worker-1");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.clientSetName("worker-1");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.client_setname("worker-1")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Do(context.Background(), "CLIENT", "SETNAME", "worker-1").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.clientSetname("worker-1");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.client_setname("worker-1")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ECHO
Source: https://upstash.com/docs/redis/commands/connection/echo

Use `ECHO` to have the server send the given message back unchanged.

The command performs no work beyond the round trip, which makes it a simple way to verify that a connection is alive and that values survive the client library's encoding and decoding. For plain liveness checks [`PING`](/docs/redis/commands/connection/ping) is the more common choice.

## Syntax

```redis
ECHO <message>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<message>` | Yes | No | Message payload. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string |
| RESP3 | Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ECHO hello
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const response = await redis.echo("hello world");
console.log(response); // "hello world"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.echo("hello")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.echo("hello");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.echo("hello");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.echo("hello")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Echo(context.Background(), "hello").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.echo("hello");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("ECHO");
    command.arg("hello");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HELLO
Source: https://upstash.com/docs/redis/commands/connection/hello

Use `HELLO` to negotiate the protocol version of the connection and read the server handshake information.

Passing `<protover>` switches the connection to RESP2 or RESP3. RESP3 adds native map, set, double, and push replies, so commands such as [`HGETALL`](/docs/redis/commands/hash/hgetall) or [`CONFIG GET`](/docs/redis/commands/server/config-get) come back as maps instead of flat arrays, and pub/sub messages arrive as push replies that do not block ordinary commands. `AUTH` authenticates in the same call and `SETNAME` names the connection, which lets a client complete its handshake in one round trip.

Called without arguments, `HELLO` only reports the server version, the protocol in use, the connection id, and the current role, leaving the protocol unchanged.

## Syntax

```redis
HELLO
  [<protover>
    [AUTH <username> <password>]
    [SETNAME <clientname>]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<protover> [AUTH <username> <password>] [SETNAME <clientname>]` | No | No | Protocol version to switch to, optionally with credentials and a connection name. |

## Important points

* This is a connection-oriented command and is available over native Redis TCP, not the stateless REST endpoint.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Flat array of alternating keys and values |
| RESP3 | Map |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HELLO
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hello();
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hello();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hello()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Do(context.Background(), "HELLO").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import java.nio.charset.StandardCharsets;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.commands.ProtocolCommand;

ProtocolCommand command = () -> "HELLO".getBytes(StandardCharsets.UTF_8);
try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sendCommand(command);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("HELLO");

    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# Connection commands
Source: https://upstash.com/docs/redis/commands/connection/overview

<CardGroup cols={2}>
<Card title="AUTH" href="/redis/commands/connection/auth">Authenticate to the server</Card>
<Card title="CLIENT GETNAME" href="/redis/commands/connection/client-getname">Get the current connection name</Card>
<Card title="CLIENT ID" href="/redis/commands/connection/client-id">Get the current client ID</Card>
<Card title="CLIENT INFO" href="/redis/commands/connection/client-info">Get info about current connection</Card>
<Card title="CLIENT LIST" href="/redis/commands/connection/client-list">List all client connections</Card>
<Card title="CLIENT SETINFO" href="/redis/commands/connection/client-setinfo">Set client connection attributes</Card>
<Card title="CLIENT SETNAME" href="/redis/commands/connection/client-setname">Set the connection name</Card>
<Card title="ECHO" href="/redis/commands/connection/echo">Echo the given string</Card>
<Card title="HELLO" href="/redis/commands/connection/hello">Handshake with Redis protocol</Card>
<Card title="PING" href="/redis/commands/connection/ping">Ping the server</Card>
<Card title="QUIT" href="/redis/commands/connection/quit">Close the connection</Card>
<Card title="RESET" href="/redis/commands/connection/reset">Reset the connection</Card>
<Card title="SELECT" href="/redis/commands/connection/select">Select the database by index</Card>
</CardGroup>

# PING
Source: https://upstash.com/docs/redis/commands/connection/ping

Use `PING` to check that the connection and the server are alive.

Without arguments the server replies `PONG`. With a message it echoes that message back instead, which lets a client match a reply to the exact request that produced it. `PING` is the standard health check for a connection pool, both to test a connection before handing it out and to keep an otherwise idle connection from being closed by intermediate proxies. It also works while the connection is subscribed to channels, where it doubles as a keepalive.

## Syntax

```redis
PING [<message>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<message>` | No | No | Message payload. |

## Important points

* Without arguments the reply is `PONG`; with a message, the message is echoed back as a bulk string.
* While the connection is subscribed under RESP2, `PING` replies with a two-element array holding `pong` and the message instead.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `PONG`, or the message as a bulk string; a two-element `pong`/message array while subscribed |
| RESP3 | Simple string `PONG`, or the message as a bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
PING
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const response = await redis.ping();
console.log(response); // "PONG"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.ping()
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.ping();
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.ping();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.ping()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Ping(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.ping();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.ping()?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# QUIT
Source: https://upstash.com/docs/redis/commands/connection/quit

<Warning>
  Prefer closing the connection from the client in new code, which avoids leaving `TIME_WAIT` sockets on the server.
</Warning>

Use `QUIT` to ask the server to close the connection once all pending replies have been sent.

The server replies `OK` and then terminates the connection, so no reply is lost and the shutdown is clean from both sides. Modern clients usually just close the socket, and pooled connections should be returned to the pool rather than closed, so `QUIT` is mostly useful in scripts and interactive sessions.

## Syntax

```redis
QUIT
```

## Arguments

This command takes no arguments.

## Important points

* This is a connection-oriented command and is available over native Redis TCP, not the stateless REST endpoint.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
QUIT
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.quit();
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sendCommand(["QUIT"]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.quit()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Quit(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import java.nio.charset.StandardCharsets;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.commands.ProtocolCommand;

ProtocolCommand command = () -> "QUIT".getBytes(StandardCharsets.UTF_8);
try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sendCommand(command);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("QUIT");

    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# RESET
Source: https://upstash.com/docs/redis/commands/connection/reset

Use `RESET` to return the connection to the state it had right after connecting.

It discards an open [`MULTI`](/docs/redis/commands/transactions/multi) block, unwatches every key watched with [`WATCH`](/docs/redis/commands/transactions/watch), leaves subscriber and monitor modes, clears the connection name, re-enables replies, and de-authenticates the connection when the database requires a password. This makes it the safe way to hand a connection back to a pool after an error, since the next user cannot inherit a half-finished transaction or a leftover subscription.

## Syntax

```redis
RESET
```

## Arguments

This command takes no arguments.

## Important points

* This is a connection-oriented command and is available over native Redis TCP, not the stateless REST endpoint.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `RESET` |
| RESP3 | Simple string `RESET` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
RESET
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.reset();
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sendCommand(["RESET"]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.reset()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Do(context.Background(), "RESET").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.reset();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("RESET");

    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SELECT
Source: https://upstash.com/docs/redis/commands/connection/select

Use `SELECT` to switch the connection to another database index.

Upstash exposes a single logical database, so `0` is the only valid index and anything else returns an error. The command is accepted so that clients and frameworks that issue `SELECT 0` while setting up a connection keep working unchanged. To separate concerns inside one database, use key prefixes instead of numbered databases.

## Syntax

```redis
SELECT 0
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `0` | Yes | No | Database index. Upstash supports only database 0. |

## Important points

* Upstash exposes a single logical database. Any index other than `0` returns an error.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SELECT 0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.select("0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sendCommand(["SELECT", "0"]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.select("0")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Do(context.Background(), "SELECT", "0").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.select(0);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("SELECT");
    command.arg("0");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# FCALL
Source: https://upstash.com/docs/redis/commands/functions/fcall

Use `FCALL` to invoke a function from a library loaded with [`FUNCTION LOAD`](/docs/redis/commands/functions/function-load).

`<numkeys>` tells the server how many of the arguments that follow are key names. Those keys reach the function in `KEYS` and every remaining argument in `ARGV`. Passing key names as keys instead of hardcoding them in the function body matters, because Redis uses that list for routing and access checks.

The function runs on the server as a single atomic step, so a sequence of reads and writes that would otherwise need several round trips and a transaction becomes one command. Use [`FCALL_RO`](/docs/redis/commands/functions/fcall-ro) when the function only reads. Functions are the successor to [`EVAL`](/docs/redis/commands/scripting/eval) scripts: they are named, registered once as part of a library, and persisted with the dataset instead of being sent or looked up by digest on every call.

Upstash runs a function under the global lock by default, since the engine cannot know in advance which keys it will touch. Registering the function with the `allow-key-locking` flag makes the call lock only the keys passed in the key list, so calls that work on disjoint keys run in parallel:

```lua
redis.register_function{
  function_name='incr_quota',
  callback=incr_quota,
  flags={'allow-key-locking'}
}
```

Unlike Lua scripts, where the flag goes on the library shebang, this flag is set per registered function. With it set, every key the function touches must be passed as a key in the `FCALL` call: keys sent as ordinary arguments are not locked, and commands that need database-wide access, such as `FLUSHDB`, are rejected. See [Key-Based Locking](/docs/redis/features/key-locking) for the full rules.

<Warning>
  Pass every key the function touches in the key list, even when it runs under
  the global lock. Upstash keeps idle entries
  [on disk](/docs/redis/features/durability): keys given in the key list are loaded
  before the function starts and the lock is released during that read, but a
  key that the function builds from `ARGV` while it runs is read from disk with
  the lock held, stalling every command waiting on it. See
  [Dynamic Keys and Latency](/docs/redis/features/key-locking#dynamic-keys-and-latency).
</Warning>

## Syntax

```redis
FCALL <function> <numkeys> [<key> [<key> ...]] [<arg> [<arg> ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<function>` | Yes | No | Name of the registered function to call. |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | No | Yes | Redis key targeted by the command. |
| `<arg>` | No | Yes | Additional argument passed to the function. |

## Important points

* `numkeys` must equal the number of key arguments that immediately follow it; remaining arguments are available to the script or function as ordinary arguments.
* The function takes the global lock unless it was registered with the `allow-key-locking` flag, in which case only the keys passed in the key list are locked. See [Key-Based Locking](/docs/redis/features/key-locking).
* Pass every key the function touches in the key list whether or not `allow-key-locking` is set. A key built inside the function is read from disk under the lock when it is not in memory, and it is rejected outright when the flag is set. See [Dynamic Keys and Latency](/docs/redis/features/key-locking#dynamic-keys-and-latency).

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Reply returned by the invoked function |
| RESP3 | Reply returned by the invoked function |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
FCALL my_function 1 my-key value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const code = `
#!lua name=mylib
redis.register_function('helloworld',
  function()
    return 'Hello World!'
  end
)
`;

await redis.functions.load({ code, replace: true });

const res = await redis.functions.call("helloworld");
console.log(res); // "Hello World!"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.fcall("my_function", "1", "my-key", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.fCall("my_function", { keys: ["my-key"], arguments: ["value"] });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.fcall("my_function", 1, "my-key", "value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.FCall(context.Background(), "my_function", []string{"my-key"}, "value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.fcall("my_function", java.util.List.of("my-key"), java.util.List.of("value"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("FCALL");
    command.arg("my_function");
    command.arg("1");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# FCALL_RO
Source: https://upstash.com/docs/redis/commands/functions/fcall-ro

Use `FCALL_RO` to invoke a function that is declared read-only.

The function must have been registered with the `no-writes` flag; calling a function without it returns an error. In exchange the server knows the call cannot modify data, so it can serve it on replicas and reject accidental writes outright.

Apart from that restriction it behaves like [`FCALL`](/docs/redis/commands/functions/fcall): `<numkeys>` splits the arguments into the keys the function receives in `KEYS` and the plain arguments it receives in `ARGV`.

Being read-only does not by itself make the call concurrent with others. The function takes the global lock unless it was also registered with the `allow-key-locking` flag, as in `flags={'no-writes', 'allow-key-locking'}`. With both flags, the call takes shared read locks on the keys passed in the key list, so several readers of the same key proceed together. See [Key-Based Locking](/docs/redis/features/key-locking).

## Syntax

```redis
FCALL_RO <function> <numkeys> [<key> [<key> ...]] [<arg> [<arg> ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<function>` | Yes | No | Name of the registered function to call. |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | No | Yes | Redis key targeted by the command. |
| `<arg>` | No | Yes | Additional argument passed to the function. |

## Important points

* `numkeys` must equal the number of key arguments that immediately follow it; remaining arguments are available to the script or function as ordinary arguments.
* A `no-writes` function still takes the global lock unless it was also registered with the `allow-key-locking` flag. See [Key-Based Locking](/docs/redis/features/key-locking).
* Pass every key the function reads in the key list whether or not `allow-key-locking` is set. A key built inside the function is read from disk under the lock when it is not in memory, and it is rejected outright when the flag is set. See [Dynamic Keys and Latency](/docs/redis/features/key-locking#dynamic-keys-and-latency).

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Reply returned by the invoked read-only function |
| RESP3 | Reply returned by the invoked read-only function |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
FCALL_RO my_function 1 my-key value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const code = `
#!lua name=ro_lib

local function get_value(keys, args)
  return redis.call('GET', keys[1])
end

redis.register_function({
  function_name='get_value',
  callback=get_value,
  flags={ 'no-writes' }
})
`;

await redis.functions.load({ code, replace: true });

// Call the read-only function
// Note: We can modify the keys usage here, but since it represents a read-only operation
// and we marked it with 'no-writes', it is safe to use callRo.
const value = await redis.functions.callRo("get_value", ["mykey"])
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.fcall_ro("my_function", "1", "my-key", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.fCallRo("my_function", { keys: ["my-key"], arguments: ["value"] });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.fcall_ro("my_function", 1, "my-key", "value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.FCallRO(context.Background(), "my_function", []string{"my-key"}, "value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.fcallReadonly("my_function", java.util.List.of("my-key"), java.util.List.of("value"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("FCALL_RO");
    command.arg("my_function");
    command.arg("1");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# FUNCTION DELETE
Source: https://upstash.com/docs/redis/commands/functions/function-delete

Use `FUNCTION DELETE` to remove a function library and every function it registered.

The argument is the library name declared when the library was loaded, not the name of a single function, and there is no way to delete one function from a library: reload the library with `REPLACE` instead. Deleting a library that does not exist returns an error, and calls to its functions fail until the library is loaded again.

## Syntax

```redis
FUNCTION DELETE <library-name>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<library-name>` | Yes | No | Name of the function library. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
FUNCTION DELETE mylib
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.functions.delete("mylib")
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.function("DELETE", "library-name");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.functionDelete("library-name");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.function_delete("library-name")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.FunctionDelete(context.Background(), "library-name").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.functionDelete("library-name");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("FUNCTION");
    command.arg("DELETE");
    command.arg("library-name");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# FUNCTION FLUSH
Source: https://upstash.com/docs/redis/commands/functions/function-flush

Use `FUNCTION FLUSH` to remove every function library from the database.

This wipes all registered libraries and functions at once and cannot be undone, so it belongs in test setup and provisioning tooling rather than application code. `ASYNC` reclaims the memory in the background and `SYNC` reclaims it before the reply is sent.

## Syntax

```redis
FUNCTION FLUSH [ASYNC | SYNC]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `(ASYNC \| SYNC)` | No | No | Choose one form: `ASYNC` (request asynchronous cleanup); `SYNC` (request synchronous cleanup). |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
FUNCTION FLUSH
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.functions.flush()
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.function("FLUSH");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.functionFlush();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.function_flush()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.FunctionFlush(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.functionFlush();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("FUNCTION");
    command.arg("FLUSH");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# FUNCTION KILL
Source: https://upstash.com/docs/redis/commands/functions/function-kill

Use `FUNCTION KILL` to stop a function that is currently running and has not yet written anything.

A function that has already modified data cannot be killed, because stopping it halfway would leave the dataset in a state that no atomic step could produce. Check [`FUNCTION STATS`](/docs/redis/commands/functions/function-stats) to see whether a function is running before calling this.

The current Upstash deployment recognizes the command but has no interruptible running-function state to act on, so it replies with a `NOTBUSY` error.

## Syntax

```redis
FUNCTION KILL
```

## Arguments

This command takes no arguments.

## Important points

* The current deployment recognizes this command but reports `NOTBUSY` because it does not expose an interruptible running-function state.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Error reply (`NOTBUSY` on the current deployment) |
| RESP3 | Error reply (`NOTBUSY` on the current deployment) |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
FUNCTION KILL
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.function("KILL");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.functionKill();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.function_kill()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.FunctionKill(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.functionKill();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("FUNCTION");
    command.arg("KILL");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# FUNCTION LIST
Source: https://upstash.com/docs/redis/commands/functions/function-list

Use `FUNCTION LIST` to inspect the function libraries loaded in the database.

The reply describes each library with its name, the engine it runs on, and the functions it registers, including each function's description and flags such as `no-writes` and [`allow-key-locking`](/docs/redis/features/key-locking), which is the way to check whether a deployed function locks only its keys or the whole database. `LIBRARYNAME` filters the reply to library names matching a pattern, and `WITHCODE` includes the full source of each library, which is how you recover the code of a library that is deployed but no longer at hand.

## Syntax

```redis
FUNCTION LIST [LIBRARYNAME <library-name-pattern>] [WITHCODE]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `LIBRARYNAME <library-name-pattern>` | No | No | Return only libraries whose name matches this pattern. |
| `WITHCODE` | No | No | Include each library's source code. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of flat arrays containing library metadata |
| RESP3 | Array of maps containing library metadata |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
FUNCTION LIST
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const libs = await redis.functions.list({
    libraryName: "mylib",
    withCode: true
})

console.log(libs)
// [
//   {
//     libraryName: "mylib",
//     engine: "LUA",
//     functions: [{
//       name: "my_func",
//       description: null,
//       flags: [ "no-writes" ]
//     }],
//     libraryCode: "#!lua name=mylib ..."
//   }
// ]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.function("LIST");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.functionList();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.function_list()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.FunctionList(context.Background(), redis.FunctionListQuery{}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.functionList();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("FUNCTION");
    command.arg("LIST");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# FUNCTION LOAD
Source: https://upstash.com/docs/redis/commands/functions/function-load

Use `FUNCTION LOAD` to register a library of functions in the database.

The payload is the library source code. It must begin with a shebang line naming the engine and the library, such as `#!lua name=mylib`, and register each function with `redis.register_function`, giving it a name, a callback, and optional flags such as `no-writes`. The reply is the library name.

`allow-key-locking` is one of those flags. It opts a function out of the global lock so that a call locks only the keys passed in its key list, which lets calls on disjoint keys run in parallel. Unlike Lua scripts, where the flag goes on the shebang line, it is declared per function in `redis.register_function`, and it is fixed until the library is loaded again. See [Key-Based Locking](/docs/redis/features/key-locking).

Whether or not you set that flag, write functions so that every key they touch arrives in the key list rather than being assembled from `ARGV` inside the function, since an undeclared key can force a disk read while the lock is held. See [Dynamic Keys and Latency](/docs/redis/features/key-locking#dynamic-keys-and-latency).

Loading fails when the library name is already in use unless `REPLACE` is given, which is how you deploy a new version of a library. Once loaded, functions are called by name with [`FCALL`](/docs/redis/commands/functions/fcall) or [`FCALL_RO`](/docs/redis/commands/functions/fcall-ro). Unlike scripts cached by [`SCRIPT LOAD`](/docs/redis/commands/scripting/script-load), libraries are part of the dataset, so they survive restarts and do not need to be re-sent by clients.

## Syntax

```redis
FUNCTION LOAD [REPLACE] <function-code>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `REPLACE` | No | No | Allow replacement of an existing destination. |
| `<function-code>` | Yes | No | Library source, including its `#!lua name=<library>` shebang. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string |
| RESP3 | Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
FUNCTION LOAD "#!lua name=mylib\nredis.register_function('helloworld', function() return 'Hello World!' end)"
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const code = `#!lua name=mylib

  -- Simple function that returns a string
  redis.register_function(
    'helloworld',
    function() return 'Hello World!' end
  )

  -- Complex function that modifies data with logic
  local function my_hset(keys, args)
    local hash = keys[1]
    local time = redis.call('TIME')[1]
    return redis.call('HSET', hash, '_last_modified_', time, unpack(args))
  end

  redis.register_function('my_hset', my_hset)
`;

const libraryName = await redis.functions.load({ code, replace: true });

console.log(libraryName); // "mylib"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.function("LOAD", "function-code");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.functionLoad("function-code");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.function_load("function-code")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.FunctionLoad(context.Background(), "function-code").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.functionLoad("function-code");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("FUNCTION");
    command.arg("LOAD");
    command.arg("function-code");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# FUNCTION STATS
Source: https://upstash.com/docs/redis/commands/functions/function-stats

Use `FUNCTION STATS` to read the current state of the function engine.

The reply reports the function that is running right now, if any, together with how long it has been running and the command that started it, plus per-engine counts of loaded libraries and functions. It is the usual way to check whether a long-running function is in progress before deciding to call [`FUNCTION KILL`](/docs/redis/commands/functions/function-kill).

## Syntax

```redis
FUNCTION STATS
```

## Arguments

This command takes no arguments.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Flat array of alternating keys and values |
| RESP3 | Map |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
FUNCTION STATS
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const stats = await redis.functions.stats()

console.log(stats)
// {
//   engines: {
//     LUA: {
//       librariesCount: 3,
//       functionsCount: 15
//     }
//   }
// }
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.function("STATS");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.functionStats();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.function_stats()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.FunctionStats(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.functionStats();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("FUNCTION");
    command.arg("STATS");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# Functions commands
Source: https://upstash.com/docs/redis/commands/functions/overview

<CardGroup cols={2}>
<Card title="FCALL" href="/redis/commands/functions/fcall">Call a function</Card>
<Card title="FCALL_RO" href="/redis/commands/functions/fcall-ro">Call a read-only function</Card>
<Card title="FUNCTION DELETE" href="/redis/commands/functions/function-delete">Delete a library</Card>
<Card title="FUNCTION FLUSH" href="/redis/commands/functions/function-flush">Delete all libraries</Card>
<Card title="FUNCTION KILL" href="/redis/commands/functions/function-kill">Kill a running function</Card>
<Card title="FUNCTION LIST" href="/redis/commands/functions/function-list">List all libraries</Card>
<Card title="FUNCTION LOAD" href="/redis/commands/functions/function-load">Load a library</Card>
<Card title="FUNCTION STATS" href="/redis/commands/functions/function-stats">Get function execution stats</Card>
</CardGroup>

# COPY
Source: https://upstash.com/docs/redis/commands/generic/copy

Use `COPY` to copy the value stored at one key to another key.

The destination gets an independent deep copy of the value, so later changes to either key do not affect the other, and the source key's remaining time to live is copied along with it. Any type can be copied.

By default the command does nothing and returns `0` when the destination key already exists; `REPLACE` overwrites it instead. `DB` selects the destination database index, which on Upstash is always `0`.

## Syntax

```redis
COPY <source> <destination> [DB <destination-db>] [REPLACE]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<source>` | Yes | No | Redis key used as source. |
| `<destination>` | Yes | No | Redis key used as destination. |
| `DB <destination-db>` | No | No | Index of the database to copy the key into. |
| `REPLACE` | No | No | Allow replacement of an existing destination. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: `1` if the key was copied, `0` otherwise |
| RESP3 | Integer: `1` if the key was copied, `0` otherwise |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
COPY source-key destination-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.copy("source-key", "destination-key");
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.copy("source-key", "destination-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.copy("source-key", "destination-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.copy("source-key", "destination-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.copy("source-key", "destination-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Copy(context.Background(), "source-key", "destination-key", 0, false).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.copy("source-key", "destination-key", false);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.copy("source-key", "destination-key", redis::CopyOptions::default())?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# DEL
Source: https://upstash.com/docs/redis/commands/generic/del

Use `DEL` to delete one or more keys and the values they hold, whatever their type.

The reply counts only the keys that actually existed, so deleting a key that is already gone is not an error and the count tells you how many were really removed. The memory is freed as part of the command, which for very large collections can take noticeable time; [`UNLINK`](/docs/redis/commands/generic/unlink) removes the keys just as immediately but frees their memory in the background.

## Syntax

```redis
DEL <key> [<key> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | Yes | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
DEL my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.del("key1", "key2");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.delete("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.del("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.del("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.delete("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Del(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.del("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.del("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# DUMP
Source: https://upstash.com/docs/redis/commands/generic/dump

Use `DUMP` to serialize the value stored at a key into a portable, Redis-specific binary blob.

The blob carries the value together with a version stamp and a checksum, but not the key name and not its time to live. Feeding it to [`RESTORE`](/docs/redis/commands/generic/restore) recreates the value under any key name, in the same database or in another one, which makes the pair the standard way to move or back up individual keys. A missing key returns null.

The reply is raw binary data, not text: keep it in a byte-safe container and avoid string encodings that would corrupt it.

## Syntax

```redis
DUMP <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string or Null bulk string or null array |
| RESP3 | Bulk string or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
DUMP my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.dump("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.dump("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.dump("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Dump(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.dump("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("DUMP");
    command.arg("my-key");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# EXISTS
Source: https://upstash.com/docs/redis/commands/generic/exists

Use `EXISTS` to check whether one or more keys exist.

The reply is the number of the given keys that exist, so with a single key it is simply `1` or `0`. A key listed several times is counted each time it is present, which means `EXISTS k k` returns `2` when `k` exists. Checking existence never transfers the value, so it stays cheap even for large values.

## Syntax

```redis
EXISTS <key> [<key> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | Yes | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
EXISTS my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.set("key1", "value1")
await redis.set("key2", "value2")
const keys = await redis.exists("key1", "key2", "key3");
console.log(keys) // 2
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.exists("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.exists("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.exists("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.exists("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Exists(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.exists("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.exists("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# EXPIRE
Source: https://upstash.com/docs/redis/commands/generic/expire

Use `EXPIRE` to give a key a lifetime in seconds, after which the key is deleted automatically.

Whether the expiration survives later writes depends on the command: replacing the value with [`SET`](/docs/redis/commands/string/set) clears it, while commands that modify a value in place, such as [`INCR`](/docs/redis/commands/string/incr), [`LPUSH`](/docs/redis/commands/list/lpush) or [`HSET`](/docs/redis/commands/hash/hset), leave it untouched. A negative lifetime deletes the key immediately.

The optional condition decides when the new expiration is applied: `NX` only when the key currently has none, `XX` only when it already has one, `GT` only when the new expiration is later than the current one, and `LT` only when it is earlier. Since a key without an expiration counts as living forever, `GT` never adds one and `LT` always does. The reply is `1` when the expiration was set and `0` when the key does not exist or the condition was not met.

Read the remaining lifetime with [`TTL`](/docs/redis/commands/generic/ttl) and remove it again with [`PERSIST`](/docs/redis/commands/generic/persist).

## Syntax

```redis
EXPIRE <key> <seconds> [NX | XX | GT | LT]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<seconds>` | Yes | No | Lifetime in seconds. |
| `(NX \| XX \| GT \| LT)` | No | No | Choose one form: `NX` (only when the key has no expiration); `XX` (only when the key already has one); `GT` (only when the new expiration is later than the current one); `LT` (only when it is earlier). |

## Important points

* `NX` cannot be combined with `XX`, `GT`, or `LT`, and `GT` and `LT` cannot be used together.
* A key with no expiration counts as an infinite one, so `GT` never sets an expiration on such a key and `LT` always does.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: `1` if the timeout was set, `0` otherwise |
| RESP3 | Integer: `1` if the timeout was set, `0` otherwise |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
EXPIRE my-key 1000
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.set("mykey", "Hello");
await redis.expire("mykey", 10);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.expire("my-key", 1000)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.expire("my-key", "1000");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.expire("my-key", 1000);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.expire("my-key", 1000)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Expire(context.Background(), "my-key", 1000*time.Second).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.expire("my-key", 1000);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.expire("my-key", 1000)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# EXPIREAT
Source: https://upstash.com/docs/redis/commands/generic/expireat

Use `EXPIREAT` to schedule a key for automatic deletion at a fixed point in time, given as a Unix timestamp in seconds.

It behaves exactly like [`EXPIRE`](/docs/redis/commands/generic/expire) except that the deadline is absolute rather than relative, which is what you want when several keys must expire at the same moment, such as the end of an hour or of a billing period. Computing the deadline once and reusing it also avoids the drift that repeated relative expirations introduce. A timestamp in the past deletes the key right away.

The optional condition works as it does for `EXPIRE`: `NX` only when the key has no expiration, `XX` only when it already has one, `GT` only when the new deadline is later than the current one, and `LT` only when it is earlier.

## Syntax

```redis
EXPIREAT <key> <unix-time-seconds> [NX | XX | GT | LT]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<unix-time-seconds>` | Yes | No | Expiration time as a Unix timestamp in seconds. |
| `(NX \| XX \| GT \| LT)` | No | No | Choose one form: `NX` (only when the key has no expiration); `XX` (only when the key already has one); `GT` (only when the new expiration is later than the current one); `LT` (only when it is earlier). |

## Important points

* `NX` cannot be combined with `XX`, `GT`, or `LT`, and `GT` and `LT` cannot be used together.
* A key with no expiration counts as an infinite one, so `GT` never sets an expiration on such a key and `LT` always does.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: `1` if the timeout was set, `0` otherwise |
| RESP3 | Integer: `1` if the timeout was set, `0` otherwise |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
EXPIREAT my-key 1735689600
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.set("mykey", "Hello");
const tenSecondsFromNow = Math.floor(Date.now() / 1000) + 10;
await redis.expireat("mykey", tenSecondsFromNow);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.expireat("my-key", 1735689600)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.expireat("my-key", "1735689600");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.expireAt("my-key", 1735689600);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.expireat("my-key", 1735689600)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ExpireAt(context.Background(), "my-key", time.Unix(1735689600, 0)).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.expireAt("my-key", 1735689600);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.expire_at("my-key", 1735689600)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# EXPIRETIME
Source: https://upstash.com/docs/redis/commands/generic/expiretime

Use `EXPIRETIME` to read the absolute time at which a key will expire, as a Unix timestamp in seconds.

The reply is `-1` when the key exists but has no expiration, and `-2` when the key does not exist, so the two cases can be told apart. Use [`TTL`](/docs/redis/commands/generic/ttl) when you need the remaining lifetime instead of the deadline, and [`PEXPIRETIME`](/docs/redis/commands/generic/pexpiretime) when you need millisecond precision.

## Syntax

```redis
EXPIRETIME <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Important points

* Negative integer replies are sentinel values, not durations or timestamps; see the response description for missing or persistent data.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: expiration Unix time in seconds, `-1` if the key has no expiration, `-2` if the key does not exist |
| RESP3 | Integer: expiration Unix time in seconds, `-1` if the key has no expiration, `-2` if the key does not exist |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
EXPIRETIME my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.expiretime("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.expireTime("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.expiretime("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ExpireTime(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.expireTime("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.expire_time("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# KEYS
Source: https://upstash.com/docs/redis/commands/generic/keys

Use `KEYS` to list every key in the database whose name matches a glob-style pattern.

The pattern supports `*` for any sequence of characters, `?` for a single character, `[...]` for character classes, and `\` to escape a literal, so `user:*:session` matches all session keys of all users and `*` matches everything.

The command walks the entire keyspace and returns all matches in one reply, which on a database of any real size means a long block and a very large response. Treat it as a debugging and maintenance tool: on hot paths use [`SCAN`](/docs/redis/commands/generic/scan), which accepts the same `MATCH` patterns but walks the keyspace in small batches, or keep an index of your keys in a set instead.

## Syntax

```redis
KEYS <pattern>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<pattern>` | Yes | No | Glob-style pattern to match keys against. |

## Important points

* This command can expose administrative information or make a broad destructive change. Restrict it to trusted code paths.
* This operation can inspect a large part of the database. Prefer cursor-based scans where possible and avoid unbounded use on hot paths.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string keys |
| RESP3 | Array of bulk-string keys |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
KEYS *
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const keys = await redis.keys("prefix*");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.keys("*")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.keys("*");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.keys("*");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.keys("*")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Keys(context.Background(), "*").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.keys("*");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.keys("*")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# MEMORY USAGE
Source: https://upstash.com/docs/redis/commands/generic/memory-usage

Use `MEMORY USAGE` to estimate how many bytes a key and its value occupy in memory.

The figure covers the stored data along with its internal overhead, so it is larger than the raw size of the value and is meant for comparing keys rather than for exact accounting. For aggregate types such as hashes, lists, sets, sorted sets, and streams the value is sampled instead of fully traversed: `SAMPLES` sets how many nested elements are inspected, and the value is clamped to the range this deployment supports, so it tunes the estimate rather than forcing an exact traversal. A missing key returns null.

It is the usual way to find out which keys are responsible for memory growth before deciding what to trim or restructure.

## Syntax

```redis
MEMORY USAGE <key> [SAMPLES <count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | Key whose in-memory footprint should be estimated. |
| `SAMPLES count` | No | No | Sampling count used when estimating large stream values. |

## Important points

* The result is an estimate in bytes and can change as the internal representation changes.
* A missing key returns null. The sampling count is clamped to the deployment's supported range.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer or Null bulk string or null array |
| RESP3 | Integer or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
MEMORY USAGE my-key SAMPLES 10
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.memory("USAGE", "my-key", "SAMPLES", "10");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.memoryUsage("my-key", { SAMPLES: 10 });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.memory_usage("my-key", samples=10)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.MemoryUsage(context.Background(), "my-key", 10).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.memoryUsage("my-key", 10);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("MEMORY");
    command.arg("USAGE");
    command.arg("my-key");
    command.arg("SAMPLES");
    command.arg("10");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# Generic commands
Source: https://upstash.com/docs/redis/commands/generic/overview

<CardGroup cols={2}>
<Card title="COPY" href="/redis/commands/generic/copy">Copy a key to another key</Card>
<Card title="DEL" href="/redis/commands/generic/del">Delete one or more keys</Card>
<Card title="DUMP" href="/redis/commands/generic/dump">Serialize a key's value</Card>
<Card title="EXISTS" href="/redis/commands/generic/exists">Check if keys exist</Card>
<Card title="EXPIRE" href="/redis/commands/generic/expire">Set a key's TTL in seconds</Card>
<Card title="EXPIREAT" href="/redis/commands/generic/expireat">Set expiry as Unix timestamp</Card>
<Card title="EXPIRETIME" href="/redis/commands/generic/expiretime">Get expiry as Unix timestamp</Card>
<Card title="KEYS" href="/redis/commands/generic/keys">Find keys matching a pattern</Card>
<Card title="MEMORY USAGE" href="/redis/commands/generic/memory-usage">Estimate memory used by a key</Card>
<Card title="PERSIST" href="/redis/commands/generic/persist">Remove the expiration from a key</Card>
<Card title="PEXPIRE" href="/redis/commands/generic/pexpire">Set a key's TTL in milliseconds</Card>
<Card title="PEXPIREAT" href="/redis/commands/generic/pexpireat">Set expiry as Unix ms timestamp</Card>
<Card title="PEXPIRETIME" href="/redis/commands/generic/pexpiretime">Get expiry as Unix ms timestamp</Card>
<Card title="PTTL" href="/redis/commands/generic/pttl">Get TTL in milliseconds</Card>
<Card title="RANDOMKEY" href="/redis/commands/generic/randomkey">Return a random key</Card>
<Card title="RENAME" href="/redis/commands/generic/rename">Rename a key</Card>
<Card title="RENAMENX" href="/redis/commands/generic/renamenx">Rename a key if new key doesn't exist</Card>
<Card title="RESTORE" href="/redis/commands/generic/restore">Deserialize and restore a key</Card>
<Card title="SCAN" href="/redis/commands/generic/scan">Incrementally iterate keys</Card>
<Card title="TOUCH" href="/redis/commands/generic/touch">Update last access time of keys</Card>
<Card title="TTL" href="/redis/commands/generic/ttl">Get TTL in seconds</Card>
<Card title="TYPE" href="/redis/commands/generic/type">Get the type of a key</Card>
<Card title="UNLINK" href="/redis/commands/generic/unlink">Delete keys asynchronously</Card>
<Card title="WAIT" href="/redis/commands/generic/wait">Wait for replica acknowledgements</Card>
<Card title="WAITAOF" href="/redis/commands/generic/waitaof">Wait for local and replica persistence</Card>
</CardGroup>

# PERSIST
Source: https://upstash.com/docs/redis/commands/generic/persist

Use `PERSIST` to remove the expiration from a key so that it stops being deleted automatically and lives until it is removed explicitly.

The reply is `1` when an expiration was removed and `0` when the key does not exist or had no expiration to begin with. This is how a value gets promoted from temporary to permanent without rewriting it, for example when a trial record becomes a persistent one.

## Syntax

```redis
PERSIST <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: `1` if the timeout was removed, `0` otherwise |
| RESP3 | Integer: `1` if the timeout was removed, `0` otherwise |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
PERSIST my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.persist(key);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.persist("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.persist("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.persist("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.persist("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Persist(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.persist("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.persist("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# PEXPIRE
Source: https://upstash.com/docs/redis/commands/generic/pexpire

Use `PEXPIRE` to give a key a lifetime in milliseconds, after which the key is deleted automatically.

It is the millisecond form of [`EXPIRE`](/docs/redis/commands/generic/expire) and behaves identically otherwise: replacing the value with [`SET`](/docs/redis/commands/string/set) clears the expiration, while in-place updates keep it, and a negative lifetime deletes the key immediately. The sub-second precision matters for short-lived keys such as locks and rate limit windows.

The optional condition decides when the new expiration is applied: `NX` only when the key has none, `XX` only when it already has one, `GT` only when the new expiration is later than the current one, and `LT` only when it is earlier.

## Syntax

```redis
PEXPIRE <key> <milliseconds> [NX | XX | GT | LT]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<milliseconds>` | Yes | No | Lifetime in milliseconds. |
| `(NX \| XX \| GT \| LT)` | No | No | Choose one form: `NX` (only when the key has no expiration); `XX` (only when the key already has one); `GT` (only when the new expiration is later than the current one); `LT` (only when it is earlier). |

## Important points

* `NX` cannot be combined with `XX`, `GT`, or `LT`, and `GT` and `LT` cannot be used together.
* A key with no expiration counts as an infinite one, so `GT` never sets an expiration on such a key and `LT` always does.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: `1` if the timeout was set, `0` otherwise |
| RESP3 | Integer: `1` if the timeout was set, `0` otherwise |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
PEXPIRE my-key 1000
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.pexpire(key, 60_000); // 1 minute
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.pexpire("my-key", 1000)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.pexpire("my-key", "1000");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.pExpire("my-key", 1000);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.pexpire("my-key", 1000)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.PExpire(context.Background(), "my-key", time.Second).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.pexpire("my-key", 1000);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.pexpire("my-key", 1000)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# PEXPIREAT
Source: https://upstash.com/docs/redis/commands/generic/pexpireat

Use `PEXPIREAT` to schedule a key for automatic deletion at a fixed point in time, given as a Unix timestamp in milliseconds.

It combines the absolute deadline of [`EXPIREAT`](/docs/redis/commands/generic/expireat) with the millisecond precision of [`PEXPIRE`](/docs/redis/commands/generic/pexpire), which is what you need when many keys must expire at exactly the same instant. A timestamp in the past deletes the key right away.

The optional condition works as elsewhere: `NX` only when the key has no expiration, `XX` only when it already has one, `GT` only when the new deadline is later than the current one, and `LT` only when it is earlier.

## Syntax

```redis
PEXPIREAT <key> <unix-time-milliseconds> [NX | XX | GT | LT]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<unix-time-milliseconds>` | Yes | No | Expiration time as a Unix timestamp in milliseconds. |
| `(NX \| XX \| GT \| LT)` | No | No | Choose one form: `NX` (only when the key has no expiration); `XX` (only when the key already has one); `GT` (only when the new expiration is later than the current one); `LT` (only when it is earlier). |

## Important points

* `NX` cannot be combined with `XX`, `GT`, or `LT`, and `GT` and `LT` cannot be used together.
* A key with no expiration counts as an infinite one, so `GT` never sets an expiration on such a key and `LT` always does.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: `1` if the timeout was set, `0` otherwise |
| RESP3 | Integer: `1` if the timeout was set, `0` otherwise |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
PEXPIREAT my-key 1735689600
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.set("mykey", "Hello");
const tenMinutesFromNow = Date.now() + 10 * 60 * 1000;
await redis.pexpireat("mykey", tenMinutesFromNow);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.pexpireat("my-key", 1735689600)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.pexpireat("my-key", "1735689600");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.pExpireAt("my-key", 1735689600);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.pexpireat("my-key", 1735689600)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.PExpireAt(context.Background(), "my-key", time.UnixMilli(1735689600)).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.pexpireAt("my-key", 1735689600);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.pexpire_at("my-key", 1735689600)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# PEXPIRETIME
Source: https://upstash.com/docs/redis/commands/generic/pexpiretime

Use `PEXPIRETIME` to read the absolute time at which a key will expire, as a Unix timestamp in milliseconds.

The reply is `-1` when the key exists but has no expiration and `-2` when the key does not exist. It is the millisecond form of [`EXPIRETIME`](/docs/redis/commands/generic/expiretime), and it reports a deadline rather than a remaining lifetime, which makes it the value to compare against a clock when you need to know exactly when something is due.

## Syntax

```redis
PEXPIRETIME <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Important points

* Negative integer replies are sentinel values, not durations or timestamps; see the response description for missing or persistent data.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: expiration Unix time in milliseconds, `-1` if the key has no expiration, `-2` if the key does not exist |
| RESP3 | Integer: expiration Unix time in milliseconds, `-1` if the key has no expiration, `-2` if the key does not exist |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
PEXPIRETIME my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.pexpiretime("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.pExpireTime("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.pexpiretime("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.PExpireTime(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.pexpireTime("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.pexpire_time("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# PTTL
Source: https://upstash.com/docs/redis/commands/generic/pttl

Use `PTTL` to read how much longer a key will live, in milliseconds.

The reply is `-1` when the key exists but has no expiration and `-2` when the key does not exist, so a missing key and a permanent one are easy to tell apart. It is the millisecond form of [`TTL`](/docs/redis/commands/generic/ttl), and the extra precision matters for short-lived keys such as locks, where rounding to whole seconds hides most of the remaining lifetime.

## Syntax

```redis
PTTL <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Important points

* Negative integer replies are sentinel values, not durations or timestamps; see the response description for missing or persistent data.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: remaining lifetime in milliseconds, `-1` if the key has no expiration, `-2` if the key does not exist |
| RESP3 | Integer: remaining lifetime in milliseconds, `-1` if the key has no expiration, `-2` if the key does not exist |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
PTTL my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const millis = await redis.pttl(key);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.pttl("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.pttl("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.pTTL("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.pttl("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.PTTL(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.pttl("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.pttl("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# RANDOMKEY
Source: https://upstash.com/docs/redis/commands/generic/randomkey

Use `RANDOMKEY` to get the name of a random key from the database without reading its value.

The reply is null when the database is empty. Keys are picked by sampling the keyspace rather than by drawing uniformly from it, and nothing prevents the same key from coming up repeatedly, so treat it as a way to look at a sample of your data while debugging rather than as a way to iterate over it. Use [`SCAN`](/docs/redis/commands/generic/scan) when you need to cover every key.

## Syntax

```redis
RANDOMKEY
```

## Arguments

This command takes no arguments.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array or Bulk string |
| RESP3 | Null or Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
RANDOMKEY
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const key = await redis.randomkey();
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.randomkey()
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.randomkey();
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.randomKey();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.randomkey()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.RandomKey(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.randomKey();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("RANDOMKEY");

    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# RENAME
Source: https://upstash.com/docs/redis/commands/generic/rename

Use `RENAME` to give an existing key a new name.

The value moves with the key and so does its remaining time to live, and the operation is atomic, so no client ever sees both names or neither. If a key with the destination name already exists it is overwritten and its old value is deleted. Renaming a key that does not exist returns an error.

Use [`RENAMENX`](/docs/redis/commands/generic/renamenx) when the destination must not be overwritten. A common pattern is to build a replacement value under a temporary key and then rename it over the live key, which swaps the data in one atomic step.

## Syntax

```redis
RENAME <key> <newkey>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<newkey>` | Yes | No | Redis key used as newkey. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
RENAME old-key new-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.rename("old", "new");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.rename("old-key", "new-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.rename("old-key", "new-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.rename("old-key", "new-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.rename("old-key", "new-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Rename(context.Background(), "old-key", "new-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.rename("old-key", "new-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.rename("old-key", "new-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# RENAMENX
Source: https://upstash.com/docs/redis/commands/generic/renamenx

Use `RENAMENX` to rename a key only when the new name is not already in use.

The reply is `1` when the rename happened and `0` when the destination already existed and nothing was changed; renaming a key that does not exist returns an error. Because the check and the rename are one atomic step, the command can serve as a way to claim a name: only one of several clients trying to rename onto the same destination succeeds.

## Syntax

```redis
RENAMENX <key> <newkey>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<newkey>` | Yes | No | Redis key used as newkey. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: `1` if the key was renamed, `0` if the destination already exists |
| RESP3 | Integer: `1` if the key was renamed, `0` if the destination already exists |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
RENAMENX old-key new-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const renamed = await redis.renamenx("old", "new");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.renamenx("old-key", "new-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.renamenx("old-key", "new-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.renameNX("old-key", "new-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.renamenx("old-key", "new-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.RenameNX(context.Background(), "old-key", "new-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.renamenx("old-key", "new-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.rename_nx("old-key", "new-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# RESTORE
Source: https://upstash.com/docs/redis/commands/generic/restore

Use `RESTORE` to recreate a key from a payload produced by [`DUMP`](/docs/redis/commands/generic/dump).

`<ttl>` gives the new key a lifetime in milliseconds, where `0` means no expiration; with `ABSTTL` the same number is read as an absolute Unix timestamp in milliseconds instead. The command fails if the key already exists unless `REPLACE` is given.

The payload's version stamp and checksum are verified before anything is written, so a truncated, corrupted, or foreign payload is rejected rather than loaded. `IDLETIME` and `FREQ` seed the eviction metadata of the new key so that a restored key does not automatically look freshly used.

## Syntax

```redis
RESTORE <key> <ttl> <serialized-value>
  [REPLACE]
  [ABSTTL]
  [IDLETIME <seconds>]
  [FREQ <frequency>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<ttl>` | Yes | No | Lifetime in milliseconds; `0` restores the key without an expiration. |
| `<serialized-value>` | Yes | No | Payload produced by `DUMP`. |
| `REPLACE` | No | No | Allow replacement of an existing destination. |
| `ABSTTL` | No | No | Treat `<ttl>` as an absolute Unix timestamp in milliseconds. |
| `IDLETIME <seconds>` | No | No | Set the key's idle time, in seconds. |
| `FREQ <frequency>` | No | No | Set the key's access frequency counter. |

## Important points

* This command can expose administrative information or make a broad destructive change. Restrict it to trusted code paths.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
RESTORE my-key 1 serialized-value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.restore("my-key", "1", "serialized-value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.restore("my-key", 1, "serialized-value");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.restore("my-key", 1, "serialized-value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Restore(context.Background(), "my-key", time.Millisecond, "serialized-value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.restore("my-key", 1, "serialized-value".getBytes());
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("RESTORE");
    command.arg("my-key");
    command.arg("1");
    command.arg("serialized-value");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SCAN
Source: https://upstash.com/docs/redis/commands/generic/scan

Use `SCAN` to walk through the keys of the database incrementally, a batch at a time.

Each call takes a cursor and returns the next cursor together with a batch of keys. Start with cursor `0` and keep calling with the cursor from the previous reply until the server returns `0` again, which marks the end of the iteration. Because the work is split over many short calls, `SCAN` never blocks the server the way [`KEYS`](/docs/redis/commands/generic/keys) can on a large keyspace.

`MATCH` filters the returned keys with a glob-style pattern, `COUNT` hints at how much work each call should do (a hint about effort, not a page size, so batches vary in length), and `TYPE` limits the reply to keys of one type. Filtering is applied after a batch has been read, so a call can legitimately return no keys at all while the cursor is still non-zero: only the cursor tells you when the iteration is over.

The guarantee is that every key present for the whole iteration is returned at least once. Keys added or removed while the scan runs may or may not show up, and a key can be returned more than once, so make the processing of each key idempotent. [`HSCAN`](/docs/redis/commands/hash/hscan), [`SSCAN`](/docs/redis/commands/set/sscan), and [`ZSCAN`](/docs/redis/commands/sorted-set/zscan) apply the same mechanism inside a single collection.

## Syntax

```redis
SCAN <cursor> [MATCH <pattern>] [COUNT <count>] [TYPE <type>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<cursor>` | Yes | No | Cursor returned by the previous call; start at `0`. |
| `MATCH <pattern>` | No | No | Return only elements matching this glob-style pattern. |
| `COUNT <count>` | No | No | Hint for how much work each iteration should do. |
| `TYPE <type>` | No | No | Return only keys of this type, such as `string`, `list`, or `hash`. |

## Important points

* This operation can inspect a large part of the database. Prefer cursor-based scans where possible and avoid unbounded use on hot paths.
* The cursor is opaque. Start with `0` and continue until the server returns cursor `0`; a single iteration may return no elements.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Two-element array: cursor and array of bulk-string keys |
| RESP3 | Two-element array: cursor and array of bulk-string keys |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SCAN 0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const [cursor, keys] = await redis.scan(0, { match: "*" });
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.scan(0)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.scan("0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.scan("0");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.scan(0)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, _, err := client.Scan(context.Background(), 0, "*", 0).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.scan("0");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let iter: redis::Iter<String> = connection.scan()?;
    for key in iter {
        println!("{key}");
    }
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# TOUCH
Source: https://upstash.com/docs/redis/commands/generic/touch

Use `TOUCH` to update the last access time of one or more keys without reading their values.

The reply counts how many of the given keys exist, just like [`EXISTS`](/docs/redis/commands/generic/exists), but the call also refreshes the idle time and access frequency that the LRU and LFU eviction policies rely on. That makes it a way to tell the server that a key is still in use, for example to keep a cached value from being evicted while a slower process is still going to need it. It does not change the key's expiration.

## Syntax

```redis
TOUCH <key> [<key> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | Yes | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
TOUCH my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.touch("key1", "key2", "key3");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.touch("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.touch("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.touch("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.touch("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Touch(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.touch("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("TOUCH");
    command.arg("my-key");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# TTL
Source: https://upstash.com/docs/redis/commands/generic/ttl

Use `TTL` to read how much longer a key will live, in seconds.

The reply is `-1` when the key exists but has no expiration and `-2` when the key does not exist, so these two are never confused with a real remaining lifetime. Values are rounded to whole seconds; use [`PTTL`](/docs/redis/commands/generic/pttl) for millisecond precision and [`EXPIRETIME`](/docs/redis/commands/generic/expiretime) when you want the absolute deadline rather than the time left.

## Syntax

```redis
TTL <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Important points

* Negative integer replies are sentinel values, not durations or timestamps; see the response description for missing or persistent data.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: remaining lifetime in seconds, `-1` if the key has no expiration, `-2` if the key does not exist |
| RESP3 | Integer: remaining lifetime in seconds, `-1` if the key has no expiration, `-2` if the key does not exist |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
TTL my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const seconds = await redis.ttl(key);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.ttl("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.ttl("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.ttl("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.ttl("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.TTL(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.ttl("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.ttl("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# TYPE
Source: https://upstash.com/docs/redis/commands/generic/type

Use `TYPE` to find out which data type is stored at a key.

The reply is one of `string`, `list`, `set`, `zset`, `hash`, or `stream`, and `none` when the key does not exist. It is the way to dispatch generic code over keys of mixed types, since applying a command to the wrong type fails with a `WRONGTYPE` error, and the way to inspect unfamiliar data before deciding how to read it.

## Syntax

```redis
TYPE <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string: the type name, or `none` if the key does not exist |
| RESP3 | Simple string: the type name, or `none` if the key does not exist |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
TYPE my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.set("key", "value");
const t = await redis.type("key");
console.log(t) // "string"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.type("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.type("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.type("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.type("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Type(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.type("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.key_type("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# UNLINK
Source: https://upstash.com/docs/redis/commands/generic/unlink

Use `UNLINK` to delete keys without freeing their memory in the foreground.

The keys are removed from the keyspace immediately, so from a client's point of view they are gone as soon as the command returns, but the memory of large values is reclaimed by a background thread. That makes it a safer alternative to [`DEL`](/docs/redis/commands/generic/del) for collections with many elements, where freeing memory synchronously can block the server for a noticeable time. The reply counts the keys that existed.

## Syntax

```redis
UNLINK <key> [<key> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | Yes | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
UNLINK my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.unlink("key1", "key2");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.unlink("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.unlink("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.unlink("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.unlink("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Unlink(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.unlink("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.unlink("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# WAIT
Source: https://upstash.com/docs/redis/commands/generic/wait

Use `WAIT` to block until preceding writes have been acknowledged by a number of replicas, or until a timeout expires.

The reply is the number of replicas that acknowledged, which can be lower than `<numreplicas>` when the timeout is reached, so callers must check it instead of assuming success. A timeout of `0` waits indefinitely.

`WAIT` raises the durability you can observe for a write, which is useful right before an action that must not be undone by a failover, such as replying to a payment webhook. It does not make Redis strongly consistent: an acknowledged write can still be lost if the primary and the acknowledging replicas fail together.

On Upstash the command waits for the writes enqueued before it began, including writes made by other connections, which is broader than the per-connection wording used by some Redis clients.

## Syntax

```redis
WAIT <numreplicas> <timeout>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `numreplicas` | Yes | No | Non-negative number of replicas that should acknowledge prior writes on this connection. |
| `timeout` | Yes | No | Maximum wait in milliseconds; `0` means no timeout. |

## Important points

* This deployment waits for writes enqueued before `WAIT` begins, including writes from other connections. That is broader than the per-connection wording used by Redis clients.
* The reply can be lower than `numreplicas` when the timeout expires. This improves observed replication durability but does not make Redis a strongly consistent store.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
WAIT 1 1000
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.wait("1", "1000");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.wait(1, 1000);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.wait("1", "1000")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Wait(context.Background(), 1, time.Second).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.waitReplicas(1, 1000);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("WAIT");
    command.arg("1");
    command.arg("1000");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# WAITAOF
Source: https://upstash.com/docs/redis/commands/generic/waitaof

Use `WAITAOF` to block until preceding writes have been persisted to the append-only file locally and on replicas.

`<numlocal>` is how many local acknowledgements to wait for and `<numreplicas>` how many replicas must have persisted the writes. The two-element reply gives the local count first and the replica count second, and either can come back lower than requested when the timeout expires, so both need checking. A timeout of `0` waits indefinitely.

Where [`WAIT`](/docs/redis/commands/generic/wait) confirms only that replicas received a write, `WAITAOF` confirms that it reached persistent storage, which is the stronger guarantee to ask for before acknowledging work that must survive a restart. On Upstash it waits for the writes enqueued before it began, including writes made by other connections.

## Syntax

```redis
WAITAOF <numlocal> <numreplicas> <timeout>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `numlocal` | Yes | No | Whether to wait for local persistence: `0` or `1`. |
| `numreplicas` | Yes | No | Non-negative number of replicas whose append-only files should include prior writes. |
| `timeout` | Yes | No | Maximum wait in milliseconds; `0` means no timeout. |

## Important points

* This deployment waits for writes enqueued before `WAITAOF` begins, including writes from other connections.
* The two-element reply contains the local persistence acknowledgement first and the replica persistence count second.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Two-element array of integers: local and replica acknowledgments |
| RESP3 | Two-element array of integers: local and replica acknowledgments |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
WAITAOF 1 1 1000
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("WAITAOF", "1", "1", "1000");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sendCommand(["WAITAOF", "1", "1", "1000"]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.waitaof("1", "1", "1000")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.WaitAOF(context.Background(), 1, 1, time.Second).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.waitAOF(1, 1, 1000);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("WAITAOF");
    command.arg("1");
    command.arg("1");
    command.arg("1000");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GEOADD
Source: https://upstash.com/docs/redis/commands/geo/geoadd

Use `GEOADD` to add longitude, latitude, and member triples to a geospatial index.

Each position is encoded into a 52-bit geohash and stored as the score of the member in a sorted set, so a geospatial key is an ordinary sorted set and commands such as [`ZREM`](/docs/redis/commands/sorted-set/zrem), [`ZCARD`](/docs/redis/commands/sorted-set/zcard), and [`ZSCAN`](/docs/redis/commands/sorted-set/zscan) work on it. Longitude must be between -180 and 180 and latitude between -85.05112878 and 85.05112878; anything outside those bounds returns an error. Adding a member that is already present moves it to the new position, and the encoding means coordinates read back with [`GEOPOS`](/docs/redis/commands/geo/geopos) are very close to, but not exactly, the ones you stored.

`NX` only adds members that are not there yet, `XX` only updates members that already exist, and `CH` makes the reply count every member that changed rather than only the ones that were added.

## Syntax

```redis
GEOADD <key>
  [NX | XX]
  [CH]
  <longitude> <latitude> <member>
  [<longitude> <latitude> <member> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `(NX \| XX)` | No | No | Choose one form: `NX` (only add new members, never update an existing one); `XX` (only update members that already exist). |
| `CH` | No | No | Count changed members rather than only new members. |
| `<longitude> <latitude> <member>` | Yes | Yes | Longitude, latitude, and the member they belong to. Repeat to add several members. |

## Important points

* `NX` and `XX` are mutually exclusive.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GEOADD my-key 29.0 41.0 member
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.geoadd("my-key", {
  longitude: 29.0,
  latitude: 41.0,
  member: "member",
});
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.geoadd("my-key", (29.0, 41.0, "member"))
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.geoadd("my-key", "29.0", "41.0", "member");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.geoAdd("my-key", { longitude: 29, latitude: 41, member: "member" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.geoadd("my-key", (29.0, 41.0, "member"))
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.GeoAdd(context.Background(), "my-key", &redis.GeoLocation{Longitude: 29.0, Latitude: 41.0, Name: "member"}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.geoadd("my-key", 29.0, 41.0, "member");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.geo_add("my-key", (29.0, 41.0, "member"))?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GEODIST
Source: https://upstash.com/docs/redis/commands/geo/geodist

Use `GEODIST` to get the distance between two members of a geospatial index.

The unit defaults to meters and can be set to `m`, `km`, `ft`, or `mi`. The distance is a great-circle distance computed from the stored positions assuming the Earth is a sphere, so it carries the small error of the geohash encoding and, of course, says nothing about the distance actually travelled on roads. If either member is missing from the index the reply is null.

## Syntax

```redis
GEODIST <key> <member1> <member2> [m | km | ft | mi]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<member1>` | Yes | No | First member. |
| `<member2>` | Yes | No | Second member. |
| `(m \| km \| ft \| mi)` | No | No | Distance unit: `m` (meters), `km` (kilometers), `ft` (feet), or `mi` (miles). Defaults to `m` when omitted. |

## Important points

* The distance is always returned as a bulk string, in both RESP2 and RESP3. Client libraries commonly decode it to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array or Bulk string |
| RESP3 | Null or Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GEODIST my-key member1 member2
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.geodist("my-key", "member1", "member2");
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.geodist("my-key", "member1", "member2")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.geodist("my-key", "member1", "member2");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.geoDist("my-key", "member1", "member2");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.geodist("my-key", "member1", "member2")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.GeoDist(context.Background(), "my-key", "member1", "member2", "m").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.geodist("my-key", "member1", "member2");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::geo::Unit;
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.geo_dist("my-key", "member1", "member2", Unit::Meters)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GEOHASH
Source: https://upstash.com/docs/redis/commands/geo/geohash

Use `GEOHASH` to get standard Geohash strings for members of a geospatial index.

The reply holds one 11-character string per requested member, in the order requested, with null for members that are not in the index. These are the strings used by geohash.org and by other geospatial tools, which makes the command the right way to export positions or to share them with systems that speak Geohash. They are derived from the stored 52-bit position, so they reflect the same rounding as [`GEOPOS`](/docs/redis/commands/geo/geopos).

## Syntax

```redis
GEOHASH <key> [<member> [<member> ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<member>` | No | Yes | Member name. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string geohashes or null values |
| RESP3 | Array of bulk-string geohashes or null values |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GEOHASH my-key member
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.geohash("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.geohash("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.geohash("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.geoHash("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.geohash("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.GeoHash(context.Background(), "my-key", "member").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.geohash("my-key", "member");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.geo_hash("my-key", "member")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GEOPOS
Source: https://upstash.com/docs/redis/commands/geo/geopos

Use `GEOPOS` to get the longitude and latitude of members of a geospatial index.

The reply holds one entry per requested member, in the order requested, each an array with longitude first and latitude second, and null for members that are not in the index. Positions are decoded from the stored geohash, so they are very close to but not bit-for-bit identical with the coordinates originally passed to [`GEOADD`](/docs/redis/commands/geo/geoadd).

## Syntax

```redis
GEOPOS <key> [<member> [<member> ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<member>` | No | Yes | Member name. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of coordinate-pair arrays or null values |
| RESP3 | Array of coordinate-pair arrays or null values |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GEOPOS my-key member
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.geopos("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.geopos("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.geopos("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.geoPos("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.geopos("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.GeoPos(context.Background(), "my-key", "member").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.geopos("my-key", "member");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.geo_pos("my-key", "member")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GEORADIUS
Source: https://upstash.com/docs/redis/commands/geo/georadius

<Warning>
  Prefer [`GEOSEARCH`](/docs/redis/commands/geo/geosearch) with `FROMLONLAT` and `BYRADIUS` in new code: `GEOSEARCH <key> FROMLONLAT <longitude> <latitude> BYRADIUS <radius> (m | km | ft | mi)`. In place of `STORE` and `STOREDIST`, use [`GEOSEARCHSTORE`](/docs/redis/commands/geo/geosearchstore) with the same query.
</Warning>

Use `GEORADIUS` to find the members of a geospatial index that lie within a given radius of a longitude and latitude point.

By default only member names are returned. `WITHDIST` adds each member's distance from the center in the unit of the query, `WITHCOORD` adds its coordinates, and `WITHHASH` adds its raw 52-bit geohash score. `COUNT` caps the number of results and, combined with `ANY`, lets the server stop as soon as it has enough matches instead of examining the whole area, which is faster but returns an arbitrary subset rather than the nearest ones. `ASC` and `DESC` sort the results by distance from the center.

`STORE` writes the matching members into a sorted set scored by geohash, so the result stays usable as a geospatial index, while `STOREDIST` scores them by their distance from the center, which makes the result easy to page through by proximity.

[`GEOSEARCH`](/docs/redis/commands/geo/geosearch) and [`GEOSEARCHSTORE`](/docs/redis/commands/geo/geosearchstore) do the same work and additionally support rectangular areas. Use [`GEORADIUS_RO`](/docs/redis/commands/geo/georadius-ro) if you want a form that cannot write.

## Syntax

```redis
GEORADIUS <key> <longitude> <latitude> <radius> (m | km | ft | mi)
  [WITHCOORD]
  [WITHDIST]
  [WITHHASH]
  [COUNT <count> [ANY]]
  [ASC | DESC]
  [STORE <storekey> | STOREDIST <storedistkey>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<longitude>` | Yes | No | Longitude in degrees, from -180 to 180. |
| `<latitude>` | Yes | No | Latitude in degrees, from -85.05112878 to 85.05112878. |
| `<radius>` | Yes | No | Search radius, in the unit given after it. |
| `(m \| km \| ft \| mi)` | Yes | No | Distance unit: `m` (meters), `km` (kilometers), `ft` (feet), or `mi` (miles). |
| `WITHCOORD` | No | No | Also return the longitude and latitude of each match. |
| `WITHDIST` | No | No | Also return the distance from the center, in the requested unit. |
| `WITHHASH` | No | No | Also return the raw 52-bit geohash score of each match. |
| `COUNT <count> [ANY]` | No | No | Return at most `<count>` matches. `ANY` returns as soon as enough matches are found, instead of sorting every match first. |
| `(ASC \| DESC)` | No | No | Sort the matches by distance from the center: `ASC` (nearest first) or `DESC` (farthest first). Unsorted when omitted. |
| `(STORE <storekey> \| STOREDIST <storedistkey>)` | No | No | Store the matches in a sorted set instead of returning them: `STORE` scores them by geohash, so the destination stays a geospatial index, and `STOREDIST` scores them by their distance from the center. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string members or member-detail arrays, or Integer when storing |
| RESP3 | Array of bulk-string members or member-detail arrays, or Integer when storing |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GEORADIUS my-key 29.0 41.0 1.5 m
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.georadius("my-key", 29.0, 41.0, 1.5, "M")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.georadius("my-key", "29.0", "41.0", "1.5", "m");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.geoRadius("my-key", { longitude: 29, latitude: 41 }, 1.5, "m");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.georadius("my-key", 29.0, 41.0, 1.5, "m")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.GeoRadius(context.Background(), "my-key", 29.0, 41.0, &redis.GeoRadiusQuery{Radius: 1.5, Unit: "m"}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.georadius("my-key", 29.0, 41.0, 1.5, redis.clients.jedis.args.GeoUnit.M);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::geo::{RadiusOptions, Unit};
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.geo_radius(
        "my-key",
        29.0,
        41.0,
        1.5,
        Unit::Meters,
        RadiusOptions::default(),
    )?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GEORADIUS_RO
Source: https://upstash.com/docs/redis/commands/geo/georadius-ro

<Warning>
  Prefer [`GEOSEARCH`](/docs/redis/commands/geo/geosearch) with `FROMLONLAT` and `BYRADIUS` in new code: `GEOSEARCH <key> FROMLONLAT <longitude> <latitude> BYRADIUS <radius> (m | km | ft | mi)`.
</Warning>

Use `GEORADIUS_RO` to find members within a radius of a point. It is the read-only form of [`GEORADIUS`](/docs/redis/commands/geo/georadius).

It accepts the same query and the same `WITHCOORD`, `WITHDIST`, `WITHHASH`, `COUNT`, and sorting options, but it has no `STORE` or `STOREDIST` clause, so the server knows the call cannot write and can serve it on replicas and from read-only scripts.

[`GEOSEARCH`](/docs/redis/commands/geo/geosearch) is read-only as well and also supports rectangular areas.

## Syntax

```redis
GEORADIUS_RO <key> <longitude> <latitude> <radius> (m | km | ft | mi)
  [WITHCOORD]
  [WITHDIST]
  [WITHHASH]
  [COUNT <count> [ANY]]
  [ASC | DESC]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<longitude>` | Yes | No | Longitude in degrees, from -180 to 180. |
| `<latitude>` | Yes | No | Latitude in degrees, from -85.05112878 to 85.05112878. |
| `<radius>` | Yes | No | Search radius, in the unit given after it. |
| `(m \| km \| ft \| mi)` | Yes | No | Distance unit: `m` (meters), `km` (kilometers), `ft` (feet), or `mi` (miles). |
| `WITHCOORD` | No | No | Also return the longitude and latitude of each match. |
| `WITHDIST` | No | No | Also return the distance from the center, in the requested unit. |
| `WITHHASH` | No | No | Also return the raw 52-bit geohash score of each match. |
| `COUNT <count> [ANY]` | No | No | Return at most `<count>` matches. `ANY` returns as soon as enough matches are found, instead of sorting every match first. |
| `(ASC \| DESC)` | No | No | Sort the matches by distance from the center: `ASC` (nearest first) or `DESC` (farthest first). Unsorted when omitted. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string members or member-detail arrays |
| RESP3 | Array of bulk-string members or member-detail arrays |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GEORADIUS_RO my-key 29.0 41.0 1.5 m
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.georadius_ro("my-key", 29.0, 41.0, 1.5, "M")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.georadius_ro("my-key", "29.0", "41.0", "1.5", "m");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.geoRadiusRo("my-key", { longitude: 29, latitude: 41 }, 1.5, "m");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.execute_command("GEORADIUS_RO", "my-key", "29.0", "41.0", "1.5", "m")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.GeoRadius(context.Background(), "my-key", 29.0, 41.0, &redis.GeoRadiusQuery{Radius: 1.5, Unit: "m"}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.georadiusReadonly("my-key", 29.0, 41.0, 1.5, redis.clients.jedis.args.GeoUnit.M);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("GEORADIUS_RO");
    command.arg("my-key");
    command.arg("29.0");
    command.arg("41.0");
    command.arg("1.5");
    command.arg("m");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GEORADIUSBYMEMBER
Source: https://upstash.com/docs/redis/commands/geo/georadiusbymember

<Warning>
  Prefer [`GEOSEARCH`](/docs/redis/commands/geo/geosearch) with `FROMMEMBER` and `BYRADIUS` in new code: `GEOSEARCH <key> FROMMEMBER <member> BYRADIUS <radius> (m | km | ft | mi)`. In place of `STORE` and `STOREDIST`, use [`GEOSEARCHSTORE`](/docs/redis/commands/geo/geosearchstore) with the same query.
</Warning>

Use `GEORADIUSBYMEMBER` to find the members of a geospatial index that lie within a given radius of another member.

It works exactly like [`GEORADIUS`](/docs/redis/commands/geo/georadius) except that the center is the stored position of a member instead of an explicit coordinate pair, which saves a lookup when the reference point is already in the index (a store, a driver, a landmark). The reference member itself always matches, since its distance from itself is zero, so remember to filter it out or ask for one result more than you need.

The same `WITHCOORD`, `WITHDIST`, `WITHHASH`, `COUNT`, sorting, and `STORE` or `STOREDIST` options apply.

## Syntax

```redis
GEORADIUSBYMEMBER <key> <member> <radius> (m | km | ft | mi)
  [WITHCOORD]
  [WITHDIST]
  [WITHHASH]
  [COUNT <count> [ANY]]
  [ASC | DESC]
  [STORE <storekey> | STOREDIST <storedistkey>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<member>` | Yes | No | Member name. |
| `<radius>` | Yes | No | Search radius, in the unit given after it. |
| `(m \| km \| ft \| mi)` | Yes | No | Distance unit: `m` (meters), `km` (kilometers), `ft` (feet), or `mi` (miles). |
| `WITHCOORD` | No | No | Also return the longitude and latitude of each match. |
| `WITHDIST` | No | No | Also return the distance from the center, in the requested unit. |
| `WITHHASH` | No | No | Also return the raw 52-bit geohash score of each match. |
| `COUNT <count> [ANY]` | No | No | Return at most `<count>` matches. `ANY` returns as soon as enough matches are found, instead of sorting every match first. |
| `(ASC \| DESC)` | No | No | Sort the matches by distance from the center: `ASC` (nearest first) or `DESC` (farthest first). Unsorted when omitted. |
| `(STORE <storekey> \| STOREDIST <storedistkey>)` | No | No | Store the matches in a sorted set instead of returning them: `STORE` scores them by geohash, so the destination stays a geospatial index, and `STOREDIST` scores them by their distance from the center. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string members or member-detail arrays, or Integer when storing |
| RESP3 | Array of bulk-string members or member-detail arrays, or Integer when storing |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GEORADIUSBYMEMBER my-key member 1.5 m
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.georadiusbymember("my-key", "member", 1.5, "M")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.georadiusbymember("my-key", "member", "1.5", "m");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.geoRadiusByMember("my-key", "member", 1.5, "m");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.georadiusbymember("my-key", "member", 1.5, "m")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.GeoRadiusByMember(context.Background(), "my-key", "member", &redis.GeoRadiusQuery{Radius: 1.5, Unit: "m"}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.georadiusByMember("my-key", "member", 1.5, redis.clients.jedis.args.GeoUnit.M);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::geo::{RadiusOptions, Unit};
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.geo_radius_by_member(
        "my-key",
        "member",
        1.5,
        Unit::Meters,
        RadiusOptions::default(),
    )?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GEORADIUSBYMEMBER_RO
Source: https://upstash.com/docs/redis/commands/geo/georadiusbymember-ro

<Warning>
  Prefer [`GEOSEARCH`](/docs/redis/commands/geo/geosearch) with `FROMMEMBER` and `BYRADIUS` in new code: `GEOSEARCH <key> FROMMEMBER <member> BYRADIUS <radius> (m | km | ft | mi)`.
</Warning>

Use `GEORADIUSBYMEMBER_RO` to find members within a radius of another member. It is the read-only form of [`GEORADIUSBYMEMBER`](/docs/redis/commands/geo/georadiusbymember).

The center is the stored position of the given member, which always appears among the matches at distance zero. The command accepts the same `WITHCOORD`, `WITHDIST`, `WITHHASH`, `COUNT`, and sorting options but has no `STORE` or `STOREDIST` clause, so it can be served on replicas and used from read-only scripts.

## Syntax

```redis
GEORADIUSBYMEMBER_RO <key> <member> <radius> (m | km | ft | mi)
  [WITHCOORD]
  [WITHDIST]
  [WITHHASH]
  [COUNT <count> [ANY]]
  [ASC | DESC]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<member>` | Yes | No | Member name. |
| `<radius>` | Yes | No | Search radius, in the unit given after it. |
| `(m \| km \| ft \| mi)` | Yes | No | Distance unit: `m` (meters), `km` (kilometers), `ft` (feet), or `mi` (miles). |
| `WITHCOORD` | No | No | Also return the longitude and latitude of each match. |
| `WITHDIST` | No | No | Also return the distance from the center, in the requested unit. |
| `WITHHASH` | No | No | Also return the raw 52-bit geohash score of each match. |
| `COUNT <count> [ANY]` | No | No | Return at most `<count>` matches. `ANY` returns as soon as enough matches are found, instead of sorting every match first. |
| `(ASC \| DESC)` | No | No | Sort the matches by distance from the center: `ASC` (nearest first) or `DESC` (farthest first). Unsorted when omitted. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string members or member-detail arrays |
| RESP3 | Array of bulk-string members or member-detail arrays |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GEORADIUSBYMEMBER_RO my-key member 1.5 m
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.georadiusbymember_ro("my-key", "member", 1.5, "M")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.georadiusbymember_ro("my-key", "member", "1.5", "m");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.geoRadiusByMemberRo("my-key", "member", 1.5, "m");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.execute_command("GEORADIUSBYMEMBER_RO", "my-key", "member", "1.5", "m")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.GeoRadiusByMember(context.Background(), "my-key", "member", &redis.GeoRadiusQuery{Radius: 1.5, Unit: "m"}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.georadiusByMemberReadonly("my-key", "member", 1.5, redis.clients.jedis.args.GeoUnit.M);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("GEORADIUSBYMEMBER_RO");
    command.arg("my-key");
    command.arg("member");
    command.arg("1.5");
    command.arg("m");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GEOSEARCH
Source: https://upstash.com/docs/redis/commands/geo/geosearch

Use `GEOSEARCH` to find the members of a geospatial index that fall inside a circle or a rectangle.

The center is either an existing member of the index (`FROMMEMBER`) or an explicit coordinate pair (`FROMLONLAT`). The area is either a circle of a given radius (`BYRADIUS`) or an axis-aligned box of a given width and height centered on that point (`BYBOX`), which is the shape to use when you are covering a map viewport rather than a "within N km" question.

By default only member names come back. `WITHDIST` adds the distance from the center in the unit of the query, `WITHCOORD` the member's coordinates, and `WITHHASH` its raw geohash score. `ASC` and `DESC` sort by distance, and `COUNT` caps the number of results; adding `ANY` lets the server return as soon as it has enough matches, which is faster but no longer gives you the nearest ones.

`GEOSEARCH` replaces the deprecated `GEORADIUS` and `GEORADIUSBYMEMBER` commands and is the command to use for new code. Use [`GEOSEARCHSTORE`](/docs/redis/commands/geo/geosearchstore) when the result should be stored instead of returned.

## Syntax

```redis
GEOSEARCH <key>
  (FROMMEMBER <member> | FROMLONLAT <longitude> <latitude>)
  (BYRADIUS <radius> (m | km | ft | mi) |
    BYBOX <width> <height> (m | km | ft | mi))
  [ASC | DESC]
  [COUNT <count> [ANY]]
  [WITHCOORD]
  [WITHDIST]
  [WITHHASH]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `(FROMMEMBER <member> \| FROMLONLAT <longitude> <latitude>)` | Yes | No | Where to center the search: `FROMMEMBER` uses the stored position of an existing member, `FROMLONLAT` uses the given coordinates. |
| `(BYRADIUS <radius> (m \| km \| ft \| mi) \| BYBOX <width> <height> (m \| km \| ft \| mi))` | Yes | No | The area to search: `BYRADIUS` a circle of the given radius, `BYBOX` an axis-aligned box of the given width and height centered on the search point. The unit is `m` (meters), `km` (kilometers), `ft` (feet), or `mi` (miles). |
| `(ASC \| DESC)` | No | No | Sort the matches by distance from the center: `ASC` (nearest first) or `DESC` (farthest first). Unsorted when omitted. |
| `COUNT <count> [ANY]` | No | No | Return at most `<count>` matches. `ANY` returns as soon as enough matches are found, instead of sorting every match first. |
| `WITHCOORD` | No | No | Also return the longitude and latitude of each match. |
| `WITHDIST` | No | No | Also return the distance from the center, in the requested unit. |
| `WITHHASH` | No | No | Also return the raw 52-bit geohash score of each match. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string members or member-detail arrays |
| RESP3 | Array of bulk-string members or member-detail arrays |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GEOSEARCH my-key FROMMEMBER member BYRADIUS 1.5 m
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.geosearch(
  "my-key",
  { type: "FROMMEMBER", member: "member" },
  { type: "BYRADIUS", radius: 1.5, radiusType: "M" },
  "ASC",
);
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.geosearch("my-key", member="member", radius=1.5, unit="M")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.geosearch("my-key", "FROMMEMBER", "member", "BYRADIUS", "1.5", "m");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.geoSearch("my-key", "member", { radius: 1.5, unit: "m" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.geosearch("my-key", member="member", radius=1.5, unit="m")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.GeoSearch(context.Background(), "my-key", &redis.GeoSearchQuery{Member: "member", Radius: 1.5, RadiusUnit: "m"}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.geosearch("my-key", "member", 1.5, redis.clients.jedis.args.GeoUnit.M);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("GEOSEARCH");
    command.arg("my-key");
    command.arg("member");
    command.arg("1.5");
    command.arg("m");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GEOSEARCHSTORE
Source: https://upstash.com/docs/redis/commands/geo/geosearchstore

Use `GEOSEARCHSTORE` to run the same query as [`GEOSEARCH`](/docs/redis/commands/geo/geosearch) and store the matching members in another key instead of returning them.

The destination is a sorted set holding the matches. By default their scores are the raw geohash values, so the destination is itself a valid geospatial index that can be queried further; with `STOREDIST` the score is the distance from the center in the unit of the query, which turns the result into a proximity-ordered list you can page through with [`ZRANGE`](/docs/redis/commands/sorted-set/zrange).

The destination is overwritten on every call, and it is deleted when the query matches nothing. The reply is the number of members stored. This is the usual way to materialize a "nearby" result once and then reuse it for pagination or further set operations.

## Syntax

```redis
GEOSEARCHSTORE <destination> <source>
  (FROMMEMBER <member> | FROMLONLAT <longitude> <latitude>)
  (BYRADIUS <radius> (m | km | ft | mi) |
    BYBOX <width> <height> (m | km | ft | mi))
  [ASC | DESC]
  [COUNT <count> [ANY]]
  [STOREDIST]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<destination>` | Yes | No | Redis key used as destination. |
| `<source>` | Yes | No | Redis key used as source. |
| `(FROMMEMBER <member> \| FROMLONLAT <longitude> <latitude>)` | Yes | No | Where to center the search: `FROMMEMBER` uses the stored position of an existing member, `FROMLONLAT` uses the given coordinates. |
| `(BYRADIUS <radius> (m \| km \| ft \| mi) \| BYBOX <width> <height> (m \| km \| ft \| mi))` | Yes | No | The area to search: `BYRADIUS` a circle of the given radius, `BYBOX` an axis-aligned box of the given width and height centered on the search point. The unit is `m` (meters), `km` (kilometers), `ft` (feet), or `mi` (miles). |
| `(ASC \| DESC)` | No | No | Sort the matches by distance from the center: `ASC` (nearest first) or `DESC` (farthest first). Unsorted when omitted. |
| `COUNT <count> [ANY]` | No | No | Return at most `<count>` matches. `ANY` returns as soon as enough matches are found, instead of sorting every match first. |
| `STOREDIST` | No | No | Store each match's distance from the center instead of its geohash score. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GEOSEARCHSTORE destination-key source-key FROMMEMBER member BYRADIUS 1.5 m
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.geosearchstore(
  "destination-key",
  "source-key",
  { type: "FROMMEMBER", member: "member" },
  { type: "BYRADIUS", radius: 1.5, radiusType: "M" },
  "ASC",
);
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.geosearchstore("destination-key", "source-key", member="member", radius=1.5, unit="M")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.geosearchstore("destination-key", "source-key", "FROMMEMBER", "member", "BYRADIUS", "1.5", "m");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.geoSearchStore("destination-key", "source-key", "member", { radius: 1.5, unit: "m" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.geosearchstore("destination-key", "source-key", member="member", radius=1.5, unit="m")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.GeoSearchStore(context.Background(), "source-key", "destination-key", &redis.GeoSearchStoreQuery{GeoSearchQuery: redis.GeoSearchQuery{Member: "member", Radius: 1.5, RadiusUnit: "m"}}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.geosearchStore("destination-key", "source-key", "member", 1.5, redis.clients.jedis.args.GeoUnit.M);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("GEOSEARCHSTORE");
    command.arg("destination-key");
    command.arg("source-key");
    command.arg("member");
    command.arg("1.5");
    command.arg("m");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# Geo commands
Source: https://upstash.com/docs/redis/commands/geo/overview

<CardGroup cols={2}>
<Card title="GEOADD" href="/redis/commands/geo/geoadd">Add geospatial items</Card>
<Card title="GEODIST" href="/redis/commands/geo/geodist">Get distance between two members</Card>
<Card title="GEOHASH" href="/redis/commands/geo/geohash">Get geohash strings for members</Card>
<Card title="GEOPOS" href="/redis/commands/geo/geopos">Get coordinates of members</Card>
<Card title="GEORADIUS" href="/redis/commands/geo/georadius">Find members within a radius of a point</Card>
<Card title="GEORADIUS_RO" href="/redis/commands/geo/georadius-ro">Read-only radius query</Card>
<Card title="GEORADIUSBYMEMBER" href="/redis/commands/geo/georadiusbymember">Find members within a radius of another member</Card>
<Card title="GEORADIUSBYMEMBER_RO" href="/redis/commands/geo/georadiusbymember-ro">Read-only radius query by member</Card>
<Card title="GEOSEARCH" href="/redis/commands/geo/geosearch">Search for members in an area</Card>
<Card title="GEOSEARCHSTORE" href="/redis/commands/geo/geosearchstore">Store geosearch results</Card>
</CardGroup>

# HDEL
Source: https://upstash.com/docs/redis/commands/hash/hdel

Use `HDEL` to remove one or more fields from a hash.

The reply counts only the fields that were actually present, so deleting a field that is already gone is not an error. When the last field of a hash is removed the key itself is deleted, because Redis does not keep empty collections.

## Syntax

```redis
HDEL <key> <field> [<field> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<field>` | Yes | Yes | Hash field name. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HDEL my-key field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hdel(key, 'field1', 'field2');
// returns 5
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hdel("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hdel("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hDel("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hdel("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HDel(context.Background(), "my-key", "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hdel("my-key", "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hdel("my-key", "field")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HEXISTS
Source: https://upstash.com/docs/redis/commands/hash/hexists

Use `HEXISTS` to check whether a field is present in a hash.

The reply is `1` when the field exists and `0` when either the field or the key is missing. Since the value is never transferred, this is the cheap way to test for presence, and it is also how you tell a missing field apart from a field whose value happens to be empty, which [`HGET`](/docs/redis/commands/hash/hget) cannot do.

## Syntax

```redis
HEXISTS <key> <field>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<field>` | Yes | No | Hash field name. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: `1` if the field exists, `0` otherwise |
| RESP3 | Integer: `1` if the field exists, `0` otherwise |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HEXISTS my-key field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("key", "field", "value");
const exists = await redis.hexists("key", "field");

console.log(exists); // 1
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hexists("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hexists("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hExists("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hexists("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HExists(context.Background(), "my-key", "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hexists("my-key", "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hexists("my-key", "field")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HEXPIRE
Source: https://upstash.com/docs/redis/commands/hash/hexpire

Use `HEXPIRE` to give individual hash fields a lifetime in seconds, after which those fields are removed from the hash.

Expiration here is per field, not per key: the hash itself stays alive as long as it still has fields, and the key is deleted automatically when the last surviving field expires. This makes it possible to keep short-lived and long-lived data in one hash, for example a user record whose verification code expires while the rest of the record stays.

`FIELDS <numfields>` introduces the list of fields and the count must match the number of names that follow. The optional condition works as it does on [`EXPIRE`](/docs/redis/commands/generic/expire): `NX` only when the field has no expiration, `XX` only when it already has one, `GT` only when the new expiration is later than the current one, and `LT` only when it is earlier.

The reply holds one status code per field, in order: `1` when the expiration was set, `0` when the condition prevented it, `2` when the field was deleted immediately because the given lifetime was zero or negative, and `-2` when the field does not exist.

## Syntax

```redis
HEXPIRE <key> <seconds>
  [NX | XX | GT | LT]
  FIELDS <numfields> <field> [<field> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<seconds>` | Yes | No | Lifetime in seconds. |
| `(NX \| XX \| GT \| LT)` | No | No | Choose one form: `NX` (only when the field has no expiration); `XX` (only when the field already has one); `GT` (only when the new expiration is later than the current one); `LT` (only when it is earlier). |
| `FIELDS <numfields> <field> [<field> ...]` | Yes | No | Fields to target. Give the field count first, then that many field names. |

## Important points

* `NX` cannot be combined with `XX`, `GT`, or `LT`, and `GT` and `LT` cannot be used together.
* A field with no expiration counts as an infinite one, so `GT` never sets an expiration on such a field and `LT` always does.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integer status codes, one per field |
| RESP3 | Array of integer status codes, one per field |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HEXPIRE my-key 1000 FIELDS 1 field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("my-key", "my-field", "my-value");
const expirationSet = await redis.hexpire("my-key", "my-field", 1);

console.log(expirationSet); // 1
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hexpire("my-key", "field", 1000)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hexpire("my-key", "1000", "FIELDS", "1", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hExpire("my-key", "field", 1000);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hexpire("my-key", 1000, "field")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HExpire(context.Background(), "my-key", 1000*time.Second, "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hexpire("my-key", 1000, "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hexpire("my-key", 1000, redis::ExpireOption::NONE, &["field"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HEXPIREAT
Source: https://upstash.com/docs/redis/commands/hash/hexpireat

Use `HEXPIREAT` to schedule individual hash fields for deletion at a fixed point in time, given as a Unix timestamp in seconds.

It is the absolute-deadline form of [`HEXPIRE`](/docs/redis/commands/hash/hexpire), which is what you want when several fields, or fields across several hashes, must expire at the same moment. A timestamp in the past removes the fields right away. The key is deleted when its last field expires.

`FIELDS <numfields>` introduces the field list and the count must match. The optional condition applies the deadline only in certain cases: `NX` when the field has no expiration, `XX` when it already has one, `GT` when the new deadline is later than the current one, and `LT` when it is earlier.

The reply holds one status code per field: `1` when the expiration was set, `0` when the condition prevented it, `2` when the field was deleted immediately, and `-2` when the field does not exist.

## Syntax

```redis
HEXPIREAT <key> <unix-time-seconds>
  [NX | XX | GT | LT]
  FIELDS <numfields> <field> [<field> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<unix-time-seconds>` | Yes | No | Expiration time as a Unix timestamp in seconds. |
| `(NX \| XX \| GT \| LT)` | No | No | Choose one form: `NX` (only when the field has no expiration); `XX` (only when the field already has one); `GT` (only when the new expiration is later than the current one); `LT` (only when it is earlier). |
| `FIELDS <numfields> <field> [<field> ...]` | Yes | No | Fields to target. Give the field count first, then that many field names. |

## Important points

* `NX` cannot be combined with `XX`, `GT`, or `LT`, and `GT` and `LT` cannot be used together.
* A field with no expiration counts as an infinite one, so `GT` never sets an expiration on such a field and `LT` always does.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integer status codes, one per field |
| RESP3 | Array of integer status codes, one per field |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HEXPIREAT my-key 1735689600 FIELDS 1 field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("my-key", "my-field", "my-value");
const expirationSet = await redis.hexpireat("my-key", "my-field", Math.floor(Date.now() / 1000) + 10);

console.log(expirationSet); // [1]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hexpireat("my-key", "field", 1735689600)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hexpireat("my-key", "1735689600", "FIELDS", "1", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hExpireAt("my-key", "field", 1735689600);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hexpireat("my-key", 1735689600, "field")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HExpireAt(context.Background(), "my-key", time.Unix(1735689600, 0), "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hexpireAt("my-key", 1735689600, "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hexpire_at("my-key", 1735689600, redis::ExpireOption::NONE, &["field"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HEXPIRETIME
Source: https://upstash.com/docs/redis/commands/hash/hexpiretime

Use `HEXPIRETIME` to read the absolute expiration time of hash fields, as Unix timestamps in seconds.

The reply holds one value per requested field, in order: the timestamp when the field expires, `-1` when the field exists but has no expiration, and `-2` when the field or the key does not exist. Use [`HTTL`](/docs/redis/commands/hash/httl) when you want the remaining lifetime instead of the deadline, and [`HPEXPIRETIME`](/docs/redis/commands/hash/hpexpiretime) for millisecond precision.

## Syntax

```redis
HEXPIRETIME <key> FIELDS <numfields> <field> [<field> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `FIELDS <numfields> <field> [<field> ...]` | Yes | No | Fields to target. Give the field count first, then that many field names. |

## Important points

* Negative integer replies are sentinel values, not durations or timestamps; see the response description for missing or persistent data.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of expiration timestamps or negative integer status codes, one per field |
| RESP3 | Array of expiration timestamps or negative integer status codes, one per field |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HEXPIRETIME my-key FIELDS 1 field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("my-key", "my-field", "my-value");
await redis.hexpireat("my-key", "my-field", Math.floor(Date.now() / 1000) + 10);
const expireTime = await redis.hexpiretime("my-key", "my-field");

console.log(expireTime); // e.g., [1697059200]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hexpiretime("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hexpiretime("my-key", "FIELDS", "1", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hExpireTime("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hexpiretime("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HExpireTime(context.Background(), "my-key", "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hexpireTime("my-key", "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hexpire_time("my-key", &["field"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HGET
Source: https://upstash.com/docs/redis/commands/hash/hget

Use `HGET` to read the value of a single field of a hash.

The reply is null when either the field or the whole key is missing, so the two cases cannot be told apart from the reply alone; use [`HEXISTS`](/docs/redis/commands/hash/hexists) when that difference matters. To read several fields use [`HMGET`](/docs/redis/commands/hash/hmget) rather than repeated calls, and to read all of them use [`HGETALL`](/docs/redis/commands/hash/hgetall).

## Syntax

```redis
HGET <key> <field>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<field>` | Yes | No | Hash field name. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string or Null bulk string or null array |
| RESP3 | Bulk string or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HGET my-key field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("key", {field: "value"});
const field = await redis.hget("key", "field");
console.log(field); // "value"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hget("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hget("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hGet("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hget("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HGet(context.Background(), "my-key", "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hget("my-key", "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hget("my-key", "field")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HGETALL
Source: https://upstash.com/docs/redis/commands/hash/hgetall

Use `HGETALL` to read every field and value of a hash in one call.

The reply pairs each field with its value. RESP2 flattens it into a single alternating array while RESP3 returns a map, and client libraries normally decode either form into a native dictionary. A missing key returns an empty result rather than an error.

The whole hash is transferred, so on hashes with many fields prefer [`HMGET`](/docs/redis/commands/hash/hmget) when you know which fields you need, or [`HSCAN`](/docs/redis/commands/hash/hscan) to walk the hash in batches.

## Syntax

```redis
HGETALL <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Important points

* Pair-based results may be flattened into one alternating array in RESP2 while RESP3 preserves nested pairs or a map.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Flat array of alternating keys and values |
| RESP3 | Map |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HGETALL my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("key", {
  field1: "value1",
  field2: "value2",
  });
const hash = await redis.hgetall("key");
console.log(hash); // { field1: "value1", field2: "value2" }
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hgetall("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hgetall("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hGetAll("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hgetall("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HGetAll(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hgetAll("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hgetall("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HGETDEL
Source: https://upstash.com/docs/redis/commands/hash/hgetdel

Use `HGETDEL` to read hash fields and delete them in the same atomic step.

The reply holds the previous value of each requested field, in the order requested, with null for fields that were not present. Reading and removing together removes the race that an [`HGET`](/docs/redis/commands/hash/hget) followed by an [`HDEL`](/docs/redis/commands/hash/hdel) would leave open, which makes the command a good fit for one-shot values such as one-time codes, claim tickets, or queued items keyed by name: exactly one caller gets the value.

`FIELDS <numfields>` introduces the field list and the count must match. The key is deleted when its last field is removed.

## Syntax

```redis
HGETDEL <key> FIELDS <numfields> <field> [<field> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `FIELDS <numfields> <field> [<field> ...]` | Yes | No | Fields to target. Give the field count first, then that many field names. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string values or null values, one per field |
| RESP3 | Array of bulk-string values or null values, one per field |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HGETDEL my-key FIELDS 1 field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

// Set some hash fields
await redis.hset("user:123", { name: "John", age: "30", email: "john@example.com" });

// Get and delete specific fields
const result = await redis.hgetdel("user:123", "name", "email");
console.log(result); // { name: "John", email: "john@example.com" }

// Verify fields were deleted
const name = await redis.hget("user:123", "name");
console.log(name); // null
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hgetdel("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hgetdel("my-key", "FIELDS", "1", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hGetDel("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hgetdel("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HGetDel(context.Background(), "my-key", "1", "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hgetdel("my-key", "1", "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hget_del("my-key", &["field"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HGETEX
Source: https://upstash.com/docs/redis/commands/hash/hgetex

Use `HGETEX` to read hash fields and change their expiration in the same call.

Without an expiration option it simply returns the values, like [`HMGET`](/docs/redis/commands/hash/hmget). `EX`, `PX`, `EXAT`, and `PXAT` give every requested field a new lifetime or deadline, and `PERSIST` removes the expiration so the fields stop expiring altogether.

Doing both in one command is what makes sliding expirations possible per field: reading a session attribute can extend it, with no window in which another client sees the field without its refreshed lifetime. `FIELDS <numfields>` introduces the field list and the count must match.

## Syntax

```redis
HGETEX <key>
  [EX <seconds> | PX <milliseconds> | EXAT <unix-time-seconds> |
    PXAT <unix-time-milliseconds> | PERSIST]
  FIELDS <numfields> <field> [<field> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `(EX <seconds> \| PX <milliseconds> \| EXAT <unix-time-seconds> \| PXAT <unix-time-milliseconds> \| PERSIST)` | No | No | Choose one form: `EX` (set a lifetime in seconds); `PX` (set a lifetime in milliseconds); `EXAT` (expire at a Unix timestamp in seconds); `PXAT` (expire at a Unix timestamp in milliseconds); `PERSIST` (remove the expiration). Left unchanged when omitted. |
| `FIELDS <numfields> <field> [<field> ...]` | Yes | No | Fields to target. Give the field count first, then that many field names. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string values or null values, one per field |
| RESP3 | Array of bulk-string values or null values, one per field |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HGETEX my-key FIELDS 1 field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("user:123", { name: "John", email: "john@example.com" });

// Get fields and set expiration to 60 seconds
const result = await redis.hgetex("user:123", { ex: 60 }, "name", "email");
console.log(result); // { name: "John", email: "john@example.com" }
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hgetex("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hgetex("my-key", "FIELDS", "1", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hGetEx("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hgetex("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HGetEX(context.Background(), "my-key", "1", "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hgetex("my-key", redis.clients.jedis.params.HGetExParams.hGetExParams(), "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hget_ex("my-key", &["field"], redis::Expiry::PERSIST)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HINCRBY
Source: https://upstash.com/docs/redis/commands/hash/hincrby

Use `HINCRBY` to add an integer to the number stored in a hash field and get the result.

A missing field, or a missing key, is treated as `0`, so the first call creates the hash and the field. The increment may be negative to count down. The stored value must be the string form of a 64-bit signed integer; anything else returns an error, as does an operation that would overflow the range.

Reading, adding, and writing back happen as one atomic step, so concurrent callers each receive a distinct result and no update is lost. That makes hashes a compact way to keep many related counters, such as per-status counts for one entity, under a single key.

## Syntax

```redis
HINCRBY <key> <field> <increment>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<field>` | Yes | No | Hash field name. |
| `<increment>` | Yes | No | Integer amount to add to the field. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HINCRBY my-key field 1
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("key", {
  field: 20,
  });
const after = await redis.hincrby("key", "field", 2);
console.log(after); // 22
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hincrby("my-key", "field", 1)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hincrby("my-key", "field", "1");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hIncrBy("my-key", "field", 1);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hincrby("my-key", "field", 1)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HIncrBy(context.Background(), "my-key", "field", 1).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hincrBy("my-key", "field", 1);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hincr("my-key", "field", 1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HINCRBYFLOAT
Source: https://upstash.com/docs/redis/commands/hash/hincrbyfloat

Use `HINCRBYFLOAT` to add a floating point number to the value of a hash field and get the result.

The stored value and the increment are parsed as double precision floats, and a missing field or key counts as `0`. The increment may be negative, and there is no separate decrement command. A value that is not a valid number returns an error.

The reply is the new value as a string, which client libraries usually decode into a native number. Note that the result is stored in the same textual form, so repeated increments of values that cannot be represented exactly in binary floating point accumulate the usual rounding error; keep money and similar quantities in integer units and use [`HINCRBY`](/docs/redis/commands/hash/hincrby).

## Syntax

```redis
HINCRBYFLOAT <key> <field> <increment>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<field>` | Yes | No | Hash field name. |
| `<increment>` | Yes | No | Amount to add to the field; may be a floating-point number. |

## Important points

* The value is always returned as a bulk string, in both RESP2 and RESP3. Client libraries commonly decode it to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string |
| RESP3 | Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HINCRBYFLOAT my-key field 1.5
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("key", {
  field: 20,
  });
const after = await redis.hincrby("key", "field", 2.5);
console.log(after); // 22.5
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hincrbyfloat("my-key", "field", 1.5)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hincrbyfloat("my-key", "field", "1.5");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hIncrByFloat("my-key", "field", 1.5);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hincrbyfloat("my-key", "field", 1.5)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HIncrByFloat(context.Background(), "my-key", "field", 1.5).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hincrByFloat("my-key", "field", 1.5);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hincr("my-key", "field", 1.5)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HKEYS
Source: https://upstash.com/docs/redis/commands/hash/hkeys

Use `HKEYS` to get the names of all the fields in a hash, without their values.

A missing key returns an empty list. The whole field list is built and transferred in one reply, so on large hashes prefer [`HSCAN`](/docs/redis/commands/hash/hscan) with `NOVALUES`, which walks the field names in batches instead.

## Syntax

```redis
HKEYS <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string fields |
| RESP3 | Array of bulk-string fields |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HKEYS my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("key", {
  id: 1,
  username: "chronark",
  });
const fields = await redis.hkeys("key");
console.log(fields); // ["id", "username"]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hkeys("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hkeys("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hKeys("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hkeys("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HKeys(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hkeys("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hkeys("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HLEN
Source: https://upstash.com/docs/redis/commands/hash/hlen

Use `HLEN` to get the number of fields in a hash.

The reply is `0` when the key does not exist. The count is kept by Redis rather than computed, so it is cheap whatever the size of the hash, which makes it the right way to check how big a hash has grown before deciding to read or iterate it.

## Syntax

```redis
HLEN <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HLEN my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("key", {
  id: 1,
  username: "chronark",
  });
const fields = await redis.hlen("key");
console.log(fields); // 2
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hlen("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hlen("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hLen("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hlen("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HLen(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hlen("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hlen("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HMGET
Source: https://upstash.com/docs/redis/commands/hash/hmget

Use `HMGET` to read several fields of a hash in one call.

The reply holds one entry per requested field, in the order requested, with null for fields that do not exist. Asking for fields of a key that does not exist returns a list of nulls rather than an error, so the shape of the reply is always predictable and can be zipped back onto your list of field names.

It saves the round trips of repeated [`HGET`](/docs/redis/commands/hash/hget) calls and transfers far less than [`HGETALL`](/docs/redis/commands/hash/hgetall) when you only need a few fields of a large hash.

## Syntax

```redis
HMGET <key> <field> [<field> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<field>` | Yes | Yes | Hash field name. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string values or null values, one per field |
| RESP3 | Array of bulk-string values or null values, one per field |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HMGET my-key field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("key", {
  id: 1,
  username: "chronark",
  name: "andreas"
  });
const fields = await redis.hmget("key", "username", "name");
console.log(fields); // { username: "chronark", name: "andreas" }
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hmget("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hmget("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hmGet("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hmget("my-key", ["field"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HMGet(context.Background(), "my-key", "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hmget("my-key", "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hmget("my-key", &["field"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HMSET
Source: https://upstash.com/docs/redis/commands/hash/hmset

<Warning>
  Prefer [`HSET`](/docs/redis/commands/hash/hset) with multiple field-value pairs in new code: `HSET <key> <field> <value> [<field> <value> ...]`.
</Warning>

Use `HMSET` to set several field and value pairs of a hash in one call, creating the key if it does not exist.

Existing fields are overwritten and the reply is always `OK`, so it says nothing about what changed. [`HSET`](/docs/redis/commands/hash/hset) accepts multiple pairs as well and additionally reports how many fields were new, so prefer it in new code.

## Syntax

```redis
HMSET <key> <field> <value> [<field> <value> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<field> <value>` | Yes | Yes | Field and the value to store in it. Repeat to set several fields in one call. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HMSET my-key field value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.hmset("my-key", { field: "value" });
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hmset("my-key", {"field": "value"})
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hmset("my-key", "field", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hSet("my-key", { field: "value" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hset("my-key", mapping={"field": "value"})
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HMSet(context.Background(), "my-key", "field", "value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hmset("my-key", java.util.Map.of("field", "value"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hset_multiple("my-key", &[("field", "value")])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HPERSIST
Source: https://upstash.com/docs/redis/commands/hash/hpersist

Use `HPERSIST` to remove the expiration from hash fields so that they stop being deleted automatically.

The reply holds one status code per requested field, in order: `1` when an expiration was removed, `-1` when the field exists but had no expiration, and `-2` when the field or the key does not exist. This is how a field set by [`HEXPIRE`](/docs/redis/commands/hash/hexpire) or [`HSETEX`](/docs/redis/commands/hash/hsetex) is promoted from temporary to permanent without rewriting its value.

`FIELDS <numfields>` introduces the field list and the count must match.

## Syntax

```redis
HPERSIST <key> FIELDS <numfields> <field> [<field> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `FIELDS <numfields> <field> [<field> ...]` | Yes | No | Fields to target. Give the field count first, then that many field names. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integer status codes, one per field |
| RESP3 | Array of integer status codes, one per field |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HPERSIST my-key FIELDS 1 field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("my-key", "my-field", "my-value");
await redis.hpexpire("my-key", "my-field", 1000);

const expirationRemoved = await redis.hpersist("my-key", "my-field");

console.log(expirationRemoved); // [1]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hpersist("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hpersist("my-key", "FIELDS", "1", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hPersist("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hpersist("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HPersist(context.Background(), "my-key", "1", "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hpersist("my-key", "1", "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hpersist("my-key", &["field"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HPEXPIRE
Source: https://upstash.com/docs/redis/commands/hash/hpexpire

Use `HPEXPIRE` to give individual hash fields a lifetime in milliseconds, after which those fields are removed from the hash.

It is the millisecond form of [`HEXPIRE`](/docs/redis/commands/hash/hexpire) and behaves identically otherwise: expiration is per field, the hash survives as long as it has fields, and the key disappears when the last field expires. The finer precision matters for short-lived fields such as per-field locks or rate limit windows.

`FIELDS <numfields>` introduces the field list and the count must match. The optional condition applies the new lifetime only in certain cases: `NX` when the field has no expiration, `XX` when it already has one, `GT` when the new expiration is later than the current one, and `LT` when it is earlier. The reply holds one status code per field: `1` when set, `0` when the condition prevented it, `2` when the field was deleted immediately, and `-2` when it does not exist.

## Syntax

```redis
HPEXPIRE <key> <milliseconds>
  [NX | XX | GT | LT]
  FIELDS <numfields> <field> [<field> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<milliseconds>` | Yes | No | Lifetime in milliseconds. |
| `(NX \| XX \| GT \| LT)` | No | No | Choose one form: `NX` (only when the field has no expiration); `XX` (only when the field already has one); `GT` (only when the new expiration is later than the current one); `LT` (only when it is earlier). |
| `FIELDS <numfields> <field> [<field> ...]` | Yes | No | Fields to target. Give the field count first, then that many field names. |

## Important points

* `NX` cannot be combined with `XX`, `GT`, or `LT`, and `GT` and `LT` cannot be used together.
* A field with no expiration counts as an infinite one, so `GT` never sets an expiration on such a field and `LT` always does.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integer status codes, one per field |
| RESP3 | Array of integer status codes, one per field |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HPEXPIRE my-key 1000 FIELDS 1 field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("my-key", "my-field", "my-value");
const expirationSet = await redis.hpexpire("my-key", "my-field", 1000);

console.log(expirationSet); // [1]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hpexpire("my-key", "field", 1000)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hpexpire("my-key", "1000", "FIELDS", "1", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hpExpire("my-key", "field", 1000);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hpexpire("my-key", 1000, "field")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HPExpire(context.Background(), "my-key", time.Second, "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hpexpire("my-key", 1000, "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hpexpire("my-key", 1000, redis::ExpireOption::NONE, &["field"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HPEXPIREAT
Source: https://upstash.com/docs/redis/commands/hash/hpexpireat

Use `HPEXPIREAT` to schedule individual hash fields for deletion at a fixed point in time, given as a Unix timestamp in milliseconds.

It combines the absolute deadline of [`HEXPIREAT`](/docs/redis/commands/hash/hexpireat) with millisecond precision, which is what you need when fields spread over several hashes have to expire at exactly the same instant. A timestamp in the past removes the fields right away, and the key is deleted when its last field expires.

`FIELDS <numfields>` introduces the field list and the count must match. The optional condition applies the deadline only when the field has no expiration (`NX`), already has one (`XX`), or when the new deadline is later (`GT`) or earlier (`LT`) than the current one. The reply holds one status code per field: `1` when set, `0` when the condition prevented it, `2` when the field was deleted immediately, and `-2` when it does not exist.

## Syntax

```redis
HPEXPIREAT <key> <unix-time-milliseconds>
  [NX | XX | GT | LT]
  FIELDS <numfields> <field> [<field> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<unix-time-milliseconds>` | Yes | No | Expiration time as a Unix timestamp in milliseconds. |
| `(NX \| XX \| GT \| LT)` | No | No | Choose one form: `NX` (only when the field has no expiration); `XX` (only when the field already has one); `GT` (only when the new expiration is later than the current one); `LT` (only when it is earlier). |
| `FIELDS <numfields> <field> [<field> ...]` | Yes | No | Fields to target. Give the field count first, then that many field names. |

## Important points

* `NX` cannot be combined with `XX`, `GT`, or `LT`, and `GT` and `LT` cannot be used together.
* A field with no expiration counts as an infinite one, so `GT` never sets an expiration on such a field and `LT` always does.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integer status codes, one per field |
| RESP3 | Array of integer status codes, one per field |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HPEXPIREAT my-key 1735689600 FIELDS 1 field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("my-key", "my-field", "my-value");
const expirationSet = await redis.hpexpireat("my-key", "my-field", Date.now() + 1000);

console.log(expirationSet); // [1]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hpexpireat("my-key", "field", 1735689600)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hpexpireat("my-key", "1735689600", "FIELDS", "1", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hpExpireAt("my-key", "field", 1735689600);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hpexpireat("my-key", 1735689600, "field")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HPExpireAt(context.Background(), "my-key", time.UnixMilli(1735689600), "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hpexpireAt("my-key", 1735689600, "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hpexpire_at("my-key", 1735689600, redis::ExpireOption::NONE, &["field"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HPEXPIRETIME
Source: https://upstash.com/docs/redis/commands/hash/hpexpiretime

Use `HPEXPIRETIME` to read the absolute expiration time of hash fields, as Unix timestamps in milliseconds.

The reply holds one value per requested field, in order: the timestamp at which it expires, `-1` when the field exists but has no expiration, and `-2` when the field or the key does not exist. It is the millisecond form of [`HEXPIRETIME`](/docs/redis/commands/hash/hexpiretime) and reports a deadline rather than the time left, which is the value to compare against a clock.

## Syntax

```redis
HPEXPIRETIME <key> FIELDS <numfields> <field> [<field> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `FIELDS <numfields> <field> [<field> ...]` | Yes | No | Fields to target. Give the field count first, then that many field names. |

## Important points

* Negative integer replies are sentinel values, not durations or timestamps; see the response description for missing or persistent data.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of expiration timestamps or negative integer status codes, one per field |
| RESP3 | Array of expiration timestamps or negative integer status codes, one per field |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HPEXPIRETIME my-key FIELDS 1 field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("my-key", "my-field", "my-value");
await redis.hpexpireat("my-key", "my-field", Date.now() + 1000);
const expireTime = await redis.hpexpiretime("my-key", "my-field");

console.log(expireTime); // e.g., 1697059200000
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hpexpiretime("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hpexpiretime("my-key", "FIELDS", "1", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hpExpireTime("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hpexpiretime("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HPExpireTime(context.Background(), "my-key", "1", "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hpexpireTime("my-key", "1", "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hpexpire_time("my-key", &["field"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HPTTL
Source: https://upstash.com/docs/redis/commands/hash/hpttl

Use `HPTTL` to read how much longer hash fields will live, in milliseconds.

The reply holds one value per requested field, in order: the remaining lifetime, `-1` when the field exists but has no expiration, and `-2` when the field or the key does not exist. It is the millisecond form of [`HTTL`](/docs/redis/commands/hash/httl), and the extra precision matters for fields that live for less than a second.

## Syntax

```redis
HPTTL <key> FIELDS <numfields> <field> [<field> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `FIELDS <numfields> <field> [<field> ...]` | Yes | No | Fields to target. Give the field count first, then that many field names. |

## Important points

* Negative integer replies are sentinel values, not durations or timestamps; see the response description for missing or persistent data.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of TTL values or negative integer status codes, one per field |
| RESP3 | Array of TTL values or negative integer status codes, one per field |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HPTTL my-key FIELDS 1 field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("my-key", "my-field", "my-value");
await redis.hpexpire("my-key", "my-field", 1000);
const ttl = await redis.hpttl("my-key", "my-field");

console.log(ttl); // e.g., [950]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hpttl("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hpttl("my-key", "FIELDS", "1", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hpTTL("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hpttl("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HPTTL(context.Background(), "my-key", "1", "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hpttl("my-key", "1", "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hpttl("my-key", &["field"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HRANDFIELD
Source: https://upstash.com/docs/redis/commands/hash/hrandfield

Use `HRANDFIELD` to get one or more random fields from a hash.

Without a count a single field name is returned, or null when the key does not exist. A positive count returns up to that many distinct fields, capped at the size of the hash, while a negative count returns exactly that many fields chosen independently, so the same field can come up more than once. `WITHVALUES` returns each field together with its value.

Nothing is removed from the hash, which is the difference from [`HGETDEL`](/docs/redis/commands/hash/hgetdel): use this for sampling, random selection, and quick inspection of an unfamiliar hash.

## Syntax

```redis
HRANDFIELD <key> [<count> [WITHVALUES]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<count> [WITHVALUES]` | No | No | Number of fields to return; a negative count may repeat fields. `WITHVALUES` also returns each field's value. |

## Important points

* Pair-based results may be flattened into one alternating array in RESP2 while RESP3 preserves nested pairs or a map.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, Bulk string, array of fields, or flat field/value array |
| RESP3 | Null, Bulk string, array of fields, or array of field/value pairs |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HRANDFIELD my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("key", {
  id: 1,
  username: "chronark",
  name: "andreas"
 });
const randomField = await redis.hrandfield("key");
console.log(randomField); // one of "id", "username" or  "name"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hrandfield("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hrandfield("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hRandField("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hrandfield("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HRandField(context.Background(), "my-key", 1).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hrandfield("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("HRANDFIELD");
    command.arg("my-key");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HSCAN
Source: https://upstash.com/docs/redis/commands/hash/hscan

Use `HSCAN` to iterate the fields of a hash in batches instead of reading it all at once.

Each call takes a cursor and returns the next cursor together with a batch of field and value pairs. Start at cursor `0` and keep calling with the cursor from the previous reply until the server returns `0`, which ends the iteration. Because each call does a bounded amount of work, this avoids the long single reply that [`HGETALL`](/docs/redis/commands/hash/hgetall) produces on a large hash.

`MATCH` filters field names with a glob-style pattern, `COUNT` hints at how much work each call should do, and `NOVALUES` returns field names only, which is noticeably cheaper when values are large and you do not need them. Filtering is applied after a batch has been read, so a call can return nothing while the cursor is still non-zero: only a cursor of `0` means the iteration is over. Fields present for the whole iteration are returned at least once, and fields added or removed while it runs may or may not appear.

## Syntax

```redis
HSCAN <key> <cursor> [MATCH <pattern>] [COUNT <count>] [NOVALUES]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<cursor>` | Yes | No | Cursor returned by the previous call; start at `0`. |
| `MATCH <pattern>` | No | No | Return only elements matching this glob-style pattern. |
| `COUNT <count>` | No | No | Hint for how much work each iteration should do. |
| `NOVALUES` | No | No | Return only field names, without their values. |

## Important points

* This operation can inspect a large part of the database. Prefer cursor-based scans where possible and avoid unbounded use on hot paths.
* The cursor is opaque. Start with `0` and continue until the server returns cursor `0`; a single iteration may return no elements.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Two-element array: cursor and flat field/value array, or field array with `NOVALUES` |
| RESP3 | Two-element array: cursor and flat field/value array, or field array with `NOVALUES` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HSCAN my-key 0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("key", {
  id: 1,
  username: "chronark",
  name: "andreas"
 });
const [newCursor, fields] = await redis.hscan("key", 0);
console.log(newCursor); // likely `0` since this is a very small hash
console.log(fields); // ["id", 1, "username", "chronark", "name", "andreas"]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hscan("my-key", 0)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hscan("my-key", "0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hScan("my-key", "0");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hscan("my-key", 0)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, _, err := client.HScan(context.Background(), "my-key", 0, "*", 0).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hscan("my-key", "0");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let iter: redis::Iter<(String, String)> = connection.hscan("my-key")?;
    for (field, value) in iter {
        println!("{field}: {value}");
    }
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HSET
Source: https://upstash.com/docs/redis/commands/hash/hset

Use `HSET` to set one or more fields of a hash to the given values, creating the key when it does not exist.

Existing fields are overwritten, and the reply counts only the fields that were added, not those that were updated, which is how you tell an insert from an update. Setting fields does not touch the key's time to live, so a hash with an expiration keeps it as it is written to. A field's own expiration is a different matter: writing a field clears the TTL it may have been given by [`HEXPIRE`](/docs/redis/commands/hash/hexpire) or [`HSETEX`](/docs/redis/commands/hash/hsetex).

Hashes are the compact way to store an object under one key: field access with `HSET` and [`HGET`](/docs/redis/commands/hash/hget) avoids reading and rewriting the whole value the way a serialized string would.

## Syntax

```redis
HSET <key> <field> <value> [<field> <value> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<field> <value>` | Yes | Yes | Field and the value to store in it. Repeat to set several fields in one call. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HSET my-key field value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("key", {
  id: 1,
  username: "chronark",
  name: "andreas"
  });
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hset("my-key", "field", "value")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hset("my-key", "field", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hSet("my-key", "field", "value");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hset("my-key", "field", "value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HSet(context.Background(), "my-key", "field", "value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hset("my-key", "field", "value");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hset("my-key", "field", "value")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HSETEX
Source: https://upstash.com/docs/redis/commands/hash/hsetex

Use `HSETEX` to write hash fields and set their expiration in the same atomic call.

`FIELDS <numfields>` introduces the field and value pairs and the count must match. `EX`, `PX`, `EXAT`, and `PXAT` give the written fields a lifetime or an absolute deadline, and `KEEPTTL` keeps whatever expiration those fields already had. Without any of these options the fields are written without an expiration, so a previously set one is dropped.

`FNX` writes only when none of the given fields exist and `FXX` only when all of them do, which turns the command into an atomic conditional write: create-if-absent or update-if-present, with the expiration applied in the same step. The reply is `1` when the fields were written and `0` when the condition prevented it.

## Syntax

```redis
HSETEX <key>
  [FNX | FXX]
  [EX <seconds> | PX <milliseconds> | EXAT <unix-time-seconds> |
    PXAT <unix-time-milliseconds> | KEEPTTL]
  FIELDS <numfields> <field> <value> [<field> <value> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `(FNX \| FXX)` | No | No | Write only when the condition holds: `FNX` when none of the given fields exist, `FXX` when all of them do. |
| `(EX <seconds> \| PX <milliseconds> \| EXAT <unix-time-seconds> \| PXAT <unix-time-milliseconds> \| KEEPTTL)` | No | No | Choose one form: `EX` (set a lifetime in seconds); `PX` (set a lifetime in milliseconds); `EXAT` (expire at a Unix timestamp in seconds); `PXAT` (expire at a Unix timestamp in milliseconds); `KEEPTTL` (preserve the existing key lifetime). |
| `FIELDS <numfields> <field> <value> [<field> <value> ...]` | Yes | No | Field-value pairs to set. Give the pair count first, then that many field and value pairs. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: `1` if the fields were set, `0` otherwise |
| RESP3 | Integer: `1` if the fields were set, `0` otherwise |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HSETEX my-key FIELDS 1 field value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

// Set fields with 1 hour expiration
await redis.hsetex("user:123", { expiration: { ex: 3600 } }, {
  name: "John",
  email: "john@example.com"
});
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hsetex("my-key", field="field", value="value")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hsetex("my-key", "FIELDS", "1", "field", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hSetEx("my-key", { field: "value" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hsetex("my-key", mapping={"field": "value"})
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HSetEX(context.Background(), "my-key", "1", "field", "value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hsetex("my-key", redis.clients.jedis.params.HSetExParams.hSetExParams(), "field", "value");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::{HashFieldExpirationOptions, TypedCommands};

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hset_ex(
        "my-key",
        &HashFieldExpirationOptions::default(),
        &[("field", "value")],
    )?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HSETNX
Source: https://upstash.com/docs/redis/commands/hash/hsetnx

Use `HSETNX` to set a hash field only when it does not already exist.

The reply is `1` when the field was created and `0` when it was already present and left untouched. The check and the write happen atomically, so of several clients racing to fill the same field exactly one succeeds, which makes the command a way to claim a slot inside a hash without overwriting whatever a concurrent writer put there.

## Syntax

```redis
HSETNX <key> <field> <value>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<field>` | Yes | No | Hash field name. |
| `<value>` | Yes | No | Value to store in the field. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: `1` if the field was set, `0` if it already exists |
| RESP3 | Integer: `1` if the field was set, `0` if it already exists |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HSETNX my-key field value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hsetnx("key", "id", 1)
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hsetnx("my-key", "field", "value")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hsetnx("my-key", "field", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hSetNX("my-key", "field", "value");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hsetnx("my-key", "field", "value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HSetNX(context.Background(), "my-key", "field", "value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hsetnx("my-key", "field", "value");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hset_nx("my-key", "field", "value")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HSTRLEN
Source: https://upstash.com/docs/redis/commands/hash/hstrlen

Use `HSTRLEN` to get the length in bytes of the value stored in a hash field.

The reply is `0` when either the field or the key is missing. Since the value itself is not transferred, this is how you check the size of a large field, or decide whether it is worth fetching, without paying for it.

## Syntax

```redis
HSTRLEN <key> <field>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<field>` | Yes | No | Hash field name. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HSTRLEN my-key field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const length = await redis.hstrlen("key", "field")
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hstrlen("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hstrlen("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hStrLen("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hstrlen("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HStrLen(context.Background(), "my-key", "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hstrlen("my-key", "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("HSTRLEN");
    command.arg("my-key");
    command.arg("field");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HTTL
Source: https://upstash.com/docs/redis/commands/hash/httl

Use `HTTL` to read how much longer hash fields will live, in seconds.

The reply holds one value per requested field, in order: the remaining lifetime, `-1` when the field exists but has no expiration, and `-2` when the field or the key does not exist, so a missing field is never confused with a permanent one. Use [`HPTTL`](/docs/redis/commands/hash/hpttl) for millisecond precision and [`HEXPIRETIME`](/docs/redis/commands/hash/hexpiretime) when you want the absolute deadline instead of the time left.

## Syntax

```redis
HTTL <key> FIELDS <numfields> <field> [<field> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `FIELDS <numfields> <field> [<field> ...]` | Yes | No | Fields to target. Give the field count first, then that many field names. |

## Important points

* Negative integer replies are sentinel values, not durations or timestamps; see the response description for missing or persistent data.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of TTL values or negative integer status codes, one per field |
| RESP3 | Array of TTL values or negative integer status codes, one per field |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HTTL my-key FIELDS 1 field
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("my-key", "my-field", "my-value");
await redis.hexpire("my-key", "my-field", 10);
const ttl = await redis.httl("my-key", "my-field");

console.log(ttl); // e.g., [9]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.httl("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.httl("my-key", "FIELDS", "1", "field");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hTTL("my-key", "field");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.httl("my-key", "field")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HTTL(context.Background(), "my-key", "1", "field").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.httl("my-key", "1", "field");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.httl("my-key", &["field"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# HVALS
Source: https://upstash.com/docs/redis/commands/hash/hvals

Use `HVALS` to get all the values in a hash, without their field names.

A missing key returns an empty list, and values come back in no particular order. The whole hash is transferred, so on large hashes prefer [`HSCAN`](/docs/redis/commands/hash/hscan) to walk it in batches, or [`HMGET`](/docs/redis/commands/hash/hmget) when you know which fields you need.

## Syntax

```redis
HVALS <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string values |
| RESP3 | Array of bulk-string values |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
HVALS my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.hset("key", {
  field1: "Hello",
  field2: "World",
})
const values = await redis.hvals("key")
console.log(values) // ["Hello", "World"]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.hvals("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.hvals("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.hVals("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.hvals("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.HVals(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.hvals("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.hvals("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# Hash commands
Source: https://upstash.com/docs/redis/commands/hash/overview

<CardGroup cols={2}>
<Card title="HDEL" href="/redis/commands/hash/hdel">Delete one or more hash fields</Card>
<Card title="HEXISTS" href="/redis/commands/hash/hexists">Check if a hash field exists</Card>
<Card title="HEXPIRE" href="/redis/commands/hash/hexpire">Set field TTL in seconds</Card>
<Card title="HEXPIREAT" href="/redis/commands/hash/hexpireat">Set field expiry as timestamp</Card>
<Card title="HEXPIRETIME" href="/redis/commands/hash/hexpiretime">Get field expiry as timestamp</Card>
<Card title="HGET" href="/redis/commands/hash/hget">Get the value of a hash field</Card>
<Card title="HGETALL" href="/redis/commands/hash/hgetall">Get all fields and values</Card>
<Card title="HGETDEL" href="/redis/commands/hash/hgetdel">Get and delete hash fields</Card>
<Card title="HGETEX" href="/redis/commands/hash/hgetex">Get fields and set their expiry</Card>
<Card title="HINCRBY" href="/redis/commands/hash/hincrby">Increment integer value of a field</Card>
<Card title="HINCRBYFLOAT" href="/redis/commands/hash/hincrbyfloat">Increment float value of a field</Card>
<Card title="HKEYS" href="/redis/commands/hash/hkeys">Get all fields in a hash</Card>
<Card title="HLEN" href="/redis/commands/hash/hlen">Get number of fields in a hash</Card>
<Card title="HMGET" href="/redis/commands/hash/hmget">Get values of multiple fields</Card>
<Card title="HMSET" href="/redis/commands/hash/hmset">Set multiple hash fields</Card>
<Card title="HPERSIST" href="/redis/commands/hash/hpersist">Remove field expiration</Card>
<Card title="HPEXPIRE" href="/redis/commands/hash/hpexpire">Set field TTL in milliseconds</Card>
<Card title="HPEXPIREAT" href="/redis/commands/hash/hpexpireat">Set field expiry as ms timestamp</Card>
<Card title="HPEXPIRETIME" href="/redis/commands/hash/hpexpiretime">Get field expiry as ms timestamp</Card>
<Card title="HPTTL" href="/redis/commands/hash/hpttl">Get field TTL in milliseconds</Card>
<Card title="HRANDFIELD" href="/redis/commands/hash/hrandfield">Get random fields from a hash</Card>
<Card title="HSCAN" href="/redis/commands/hash/hscan">Incrementally iterate hash fields</Card>
<Card title="HSET" href="/redis/commands/hash/hset">Set hash field values</Card>
<Card title="HSETEX" href="/redis/commands/hash/hsetex">Set fields with expiration</Card>
<Card title="HSETNX" href="/redis/commands/hash/hsetnx">Set field only if it doesn't exist</Card>
<Card title="HSTRLEN" href="/redis/commands/hash/hstrlen">Get length of a field's value</Card>
<Card title="HTTL" href="/redis/commands/hash/httl">Get field TTL in seconds</Card>
<Card title="HVALS" href="/redis/commands/hash/hvals">Get all values in a hash</Card>
</CardGroup>

# HyperLogLog commands
Source: https://upstash.com/docs/redis/commands/hyperloglog/overview

<CardGroup cols={2}>
<Card title="PFADD" href="/redis/commands/hyperloglog/pfadd">Add elements to HyperLogLog</Card>
<Card title="PFCOUNT" href="/redis/commands/hyperloglog/pfcount">Get estimated cardinality</Card>
<Card title="PFMERGE" href="/redis/commands/hyperloglog/pfmerge">Merge multiple HyperLogLogs</Card>
</CardGroup>

# PFADD
Source: https://upstash.com/docs/redis/commands/hyperloglog/pfadd

Use `PFADD` to add elements to a HyperLogLog.

A HyperLogLog estimates how many distinct items it has seen while using a small, fixed amount of memory (at most about 12 KB) no matter how many elements pass through it. That is the trade it makes: individual elements are not stored, so they cannot be listed, checked for membership, or removed, and the cardinality that comes back from [`PFCOUNT`](/docs/redis/commands/hyperloglog/pfcount) is an approximation with a standard error of about 0.81%.

The key is created on first use, and the reply is `1` when the internal registers changed as a result of the call, which is a hint that at least one element was new, and `0` when they did not. Calling `PFADD` with no elements creates an empty HyperLogLog if the key does not exist yet. Use it for counts where the exact number does not matter, such as unique visitors per page or per day, and a set for cases where you must be able to look elements up.

## Syntax

```redis
PFADD <key> [<element> [<element> ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<element>` | No | Yes | Element to add to the HyperLogLog. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: `1` if at least one internal register was altered, `0` otherwise |
| RESP3 | Integer: `1` if at least one internal register was altered, `0` otherwise |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
PFADD my-key element
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.pfadd("my-key", "element");
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.pfadd("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.pfadd("my-key", "element");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.pfAdd("my-key", "element");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.pfadd("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.PFAdd(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.pfadd("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.pfadd("my-key", &["member"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# PFCOUNT
Source: https://upstash.com/docs/redis/commands/hyperloglog/pfcount

Use `PFCOUNT` to read the estimated number of distinct elements recorded in one or more HyperLogLogs.

With a single key the stored estimate is returned. With several keys the structures are merged on the fly and the cardinality of their union is returned, without modifying any of them, which is how you answer "how many unique users across these seven days" without double counting. The result is an approximation with a standard error of about 0.81%.

The multi-key form does real work on every call, so when the same union is read often it is cheaper to roll the sources up into one key with [`PFMERGE`](/docs/redis/commands/hyperloglog/pfmerge) and count that.

## Syntax

```redis
PFCOUNT <key> [<key> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | Yes | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
PFCOUNT my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.pfcount("my-key");
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.pfcount("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.pfcount("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.pfCount("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.pfcount("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.PFCount(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.pfcount("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.pfcount(&["my-key"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# PFMERGE
Source: https://upstash.com/docs/redis/commands/hyperloglog/pfmerge

Use `PFMERGE` to merge several HyperLogLogs into a single one.

The destination ends up representing the union of the source structures and of whatever it already held, so merging the same sources again changes nothing and new data can be folded in as it arrives. The destination is created if it does not exist.

Because the union is computed register by register and loses no accuracy compared with counting the raw data, rolling hourly keys into a daily key, or daily keys into a monthly one, gives the same estimate as if every element had been added to that key directly. That makes `PFMERGE` the building block for time-based rollups of unique counts.

## Syntax

```redis
PFMERGE <destkey> [<sourcekey> [<sourcekey> ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<destkey>` | Yes | No | Redis key used as destkey. |
| `<sourcekey>` | No | Yes | Redis key used as sourcekey. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
PFMERGE destination-key source-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.pfmerge("destination-key", "source-key");
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.pfmerge("destination-key", "source-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.pfmerge("destination-key", "source-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.pfMerge("destination-key", "source-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.pfmerge("destination-key", "source-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.PFMerge(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.pfmerge("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.pfmerge("destination-key", &["source-key"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.ARRAPPEND
Source: https://upstash.com/docs/redis/commands/json/json-arrappend

Use `JSON.ARRAPPEND` to append one or more values to the end of the arrays a path selects.

Values are JSON text and each one is appended as a single element, so appending an array adds a nested array rather than merging its items. The reply is the new length of each array the path matched, with null for matches that are not arrays.

## Syntax

```redis
JSON.ARRAPPEND <key> <path> <value> [value ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | Yes | No | Path selecting arrays. |
| `value` | Yes | Yes | Valid JSON value to append. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integer lengths or null values, one per matched path |
| RESP3 | Array of integer lengths or null values, one per matched path |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.ARRAPPEND profile $.tags '"new"'
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.json.arrappend("key", "$.path.to.array", "a");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().arrappend("profile", "$.tags", "new")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.ARRAPPEND", "profile", "$.tags", "\"new\"");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.arrAppend("profile", "$.tags", "new");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().arrappend("profile", "$.tags", "new")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONArrAppend(context.Background(), "profile", "$.tags", "new").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonArrAppend("profile", new redis.clients.jedis.json.Path("$.tags"), "new");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_arr_append("profile", "$.tags", &"new")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.ARRINDEX
Source: https://upstash.com/docs/redis/commands/json/json-arrindex

Use `JSON.ARRINDEX` to find the first position of a value inside the arrays a path selects.

The value is JSON text and is compared for exact equality, so `1` does not match `"1"`. The optional `start` and `stop` bound the search: `start` is inclusive, `stop` is exclusive, both may be negative to count from the end of the array, and `0` for `stop` means "to the end". The reply is the index of the first match or `-1` when the value is not present, with one result per array the path matched.

## Syntax

```redis
JSON.ARRINDEX <key> <path> <value> [start [stop]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | Yes | No | Path selecting arrays. |
| `value` | Yes | No | Valid JSON value to locate. |
| `start` | No | No | Inclusive starting index. |
| `stop` | No | No | Exclusive ending index. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integer indexes or null values, one per matched path |
| RESP3 | Array of integer indexes or null values, one per matched path |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.ARRINDEX profile $.tags '"new"'
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const index = await redis.json.arrindex("key", "$.path.to.array", "a");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().arrindex("profile", "$.tags", "new")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.ARRINDEX", "profile", "$.tags", "\"new\"");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.arrIndex("profile", "$.tags", "new");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().arrindex("profile", "$.tags", "new")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONArrIndex(context.Background(), "profile", "$.tags", "new").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonArrIndex("profile", new redis.clients.jedis.json.Path("$.tags"), "new");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_arr_index("profile", "$.tags", &"new")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.ARRINSERT
Source: https://upstash.com/docs/redis/commands/json/json-arrinsert

Use `JSON.ARRINSERT` to insert one or more values into the arrays a path selects, before a given index.

Elements at and after that index shift to the right, keeping the rest of the array in order. A negative index counts from the end of the array and an index equal to the array's length appends, while an index outside the array returns an error. The reply is the new length of each array the path matched.

## Syntax

```redis
JSON.ARRINSERT <key> <path> <index> <value> [value ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | Yes | No | Path selecting arrays. |
| `index` | Yes | No | Insertion index; negative indexes count from the end. |
| `value` | Yes | Yes | Valid JSON value to insert. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integer lengths or null values, one per matched path |
| RESP3 | Array of integer lengths or null values, one per matched path |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.ARRINSERT profile $.tags 0 '"first"'
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const length = await redis.json.arrinsert("key", "$.path.to.array", 2, "a", "b");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().arrinsert("profile", "$.tags", 0, "first")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.ARRINSERT", "profile", "$.tags", "0", "\"first\"");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.arrInsert("profile", "$.tags", 0, "first");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().arrinsert("profile", "$.tags", 0, "first")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONArrInsert(context.Background(), "profile", "$.tags", 0, "first").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonArrInsert("profile", new redis.clients.jedis.json.Path("$.tags"), 0, "first");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_arr_insert("profile", "$.tags", 0, &"first")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.ARRLEN
Source: https://upstash.com/docs/redis/commands/json/json-arrlen

Use `JSON.ARRLEN` to get the number of elements in the arrays a path selects.

Without a path the root value is used. The reply is one length per match, with null for matches that are not arrays, so it doubles as a cheap way to check that a branch of the document really is an array before working on it.

## Syntax

```redis
JSON.ARRLEN <key> [path]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | No | No | Path selecting arrays; defaults to the root. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integer lengths or null values, one per matched path |
| RESP3 | Array of integer lengths or null values, one per matched path |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.ARRLEN profile $.tags
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const length = await redis.json.arrlen("key", "$.path.to.array");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().arrlen("profile", "$.tags")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.ARRLEN", "profile", "$.tags");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.arrLen("profile", { path: "$.tags" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().arrlen("profile", "$.tags")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONArrLen(context.Background(), "profile", "$.tags").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonArrLen("profile", new redis.clients.jedis.json.Path("$.tags"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_arr_len("profile", "$.tags")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.ARRPOP
Source: https://upstash.com/docs/redis/commands/json/json-arrpop

Use `JSON.ARRPOP` to remove an element from the arrays a path selects and return it.

Without an index the last element is popped, which makes the command a stack pop; index `0` pops the first element, and negative indexes count from the end. An index past the end of the array is clamped to the last element. The reply is the removed element as JSON text, or null when the array is empty.

Because the read and the removal happen in one atomic step, a JSON array can be used as a small work queue without the risk of two clients taking the same element.

## Syntax

```redis
JSON.ARRPOP <key> [path [index]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | No | No | Path selecting arrays; defaults to the root. |
| `index` | No | No | Element index; defaults to the last element. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string JSON values or null values, one per matched path |
| RESP3 | Array of bulk-string JSON values or null values, one per matched path |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.ARRPOP profile $.tags -1
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const element = await redis.json.arrpop("key", "$.path.to.array");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().arrpop("profile", "$.tags", -1)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.ARRPOP", "profile", "$.tags", "-1");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.arrPop("profile", { path: "$.tags", index: -1 });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().arrpop("profile", "$.tags", -1)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONArrPop(context.Background(), "profile", "$.tags", -1).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonArrPop("profile", new redis.clients.jedis.json.Path("$.tags"), -1);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_arr_pop("profile", "$.tags", -1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.ARRTRIM
Source: https://upstash.com/docs/redis/commands/json/json-arrtrim

Use `JSON.ARRTRIM` to keep only a range of elements in the arrays a path selects and discard the rest.

Both `start` and `stop` are inclusive indexes and may be negative to count from the end of the array. Indexes outside the array are clamped, and a range that selects nothing leaves an empty array. The reply is the new length of each array the path matched.

It is the JSON counterpart of [`LTRIM`](/docs/redis/commands/list/ltrim): combine it with [`JSON.ARRAPPEND`](/docs/redis/commands/json/json-arrappend) to keep a capped list, such as the last N events, inside a document.

## Syntax

```redis
JSON.ARRTRIM <key> <path> <start> <stop>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | Yes | No | Path selecting arrays. |
| `start` | Yes | No | Inclusive first index to keep. |
| `stop` | Yes | No | Inclusive last index to keep. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integer lengths or null values, one per matched path |
| RESP3 | Array of integer lengths or null values, one per matched path |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.ARRTRIM profile $.tags 0 9
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const length = await redis.json.arrtrim("key", "$.path.to.array", 2, 10);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().arrtrim("profile", "$.tags", 0, 9)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.ARRTRIM", "profile", "$.tags", "0", "9");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.arrTrim("profile", "$.tags", 0, 9);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().arrtrim("profile", "$.tags", 0, 9)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONArrTrimWithArgs(context.Background(), "profile", "$.tags", &redis.JSONArrTrimArgs{Start: 0, Stop: func() *int { stop := 9; return &stop }()}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonArrTrim("profile", new redis.clients.jedis.json.Path("$.tags"), 0, 9);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_arr_trim("profile", "$.tags", 0, 9)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.CLEAR
Source: https://upstash.com/docs/redis/commands/json/json-clear

Use `JSON.CLEAR` to empty the values a path selects without removing them from the document.

Objects lose all their keys, arrays lose all their elements, and numbers are reset to `0`. Values of other types, such as strings and booleans, are left as they are. The reply is the number of values that were cleared.

The difference from [`JSON.DEL`](/docs/redis/commands/json/json-del) is that the selected keys and slots stay in the document as empty containers, so the shape of the document is preserved and consumers that expect a field to exist keep working.

## Syntax

```redis
JSON.CLEAR <key> [path]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | No | No | Path to containers or numbers; defaults to the root. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.CLEAR profile $.stats
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.json.clear("key");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().clear("profile", "$.stats")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.CLEAR", "profile", "$.stats");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.clear("profile", { path: "$.stats" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().clear("profile", "$.stats")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONClear(context.Background(), "profile", "$.stats").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonClear("profile", new redis.clients.jedis.json.Path("$.stats"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_clear("profile", "$.stats")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.DEBUG
Source: https://upstash.com/docs/redis/commands/json/json-debug

Use `JSON.DEBUG` to inspect internal details of stored JSON values.

`JSON.DEBUG MEMORY` reports the approximate number of bytes used by the value a key and optional path select, which is how you find out which documents, or which branches of a document, are responsible for memory growth. The figure includes internal overhead and is an estimate meant for comparison rather than exact accounting. `JSON.DEBUG HELP` lists the supported forms.

It is a diagnostic aid: the details it exposes are implementation-specific and can change, so do not build application logic on them.

## Syntax

```redis
JSON.DEBUG MEMORY <key> [path]
JSON.DEBUG HELP
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `MEMORY` | One form | No | Report the approximate memory used by the JSON value selected by key and optional path. |
| `key` | For MEMORY | No | JSON document key. |
| `path` | No | No | JSONPath to inspect; defaults to the root. |
| `HELP` | One form | No | Return the supported JSON.DEBUG forms. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer, array of integers, or array of help strings |
| RESP3 | Integer, array of integers, or array of help strings |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.DEBUG MEMORY profile $.stats
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.DEBUG", "MEMORY", "profile", "$.stats");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.debugMemory("profile", { path: "$.stats" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().debug("MEMORY", "profile", "$.stats")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONDebugMemory(context.Background(), "profile", "$.stats").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonDebugMemory("profile", new redis.clients.jedis.json.Path("$.stats"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("JSON.DEBUG");
    command.arg("MEMORY");
    command.arg("profile");
    command.arg("$.stats");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.DEL
Source: https://upstash.com/docs/redis/commands/json/json-del

Use `JSON.DEL` to delete the value at a path in a JSON document.

Without a path the entire key is deleted. The reply is the number of values that were deleted, which is `0` when the path matched nothing, and deleting the root of a document removes the key itself. With a JSONPath that matches several places, every match is removed in the same call.

[`JSON.FORGET`](/docs/redis/commands/json/json-forget) is an alias with identical behavior.

## Syntax

```redis
JSON.DEL <key> [path]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | No | No | Path to delete; omitting it deletes the whole key. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.DEL profile $.temporary
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.json.del("key", "$.path.to.value");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().delete("profile", "$.temporary")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.DEL", "profile", "$.temporary");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.del("profile", { path: "$.temporary" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().delete("profile", "$.temporary")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONDel(context.Background(), "profile", "$.temporary").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonDel("profile", new redis.clients.jedis.json.Path("$.temporary"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_del("profile", "$.temporary")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.FORGET
Source: https://upstash.com/docs/redis/commands/json/json-forget

Use `JSON.FORGET` to delete the value at a path in a JSON document. It is an alias of [`JSON.DEL`](/docs/redis/commands/json/json-del) with identical behavior, kept for compatibility with clients and code that use the older name.

Without a path the entire key is deleted, and the reply is the number of values that were deleted, which is `0` when the path matched nothing.

## Syntax

```redis
JSON.FORGET <key> [path]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | No | No | Path to delete; omitting it deletes the whole key. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.FORGET profile $.temporary
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.json.forget("key", "$.path.to.value");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().forget("profile", "$.temporary")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.FORGET", "profile", "$.temporary");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.forget("profile", { path: "$.temporary" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().forget("profile", "$.temporary")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONForget(context.Background(), "profile", "$.temporary").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonDel("profile", new redis.clients.jedis.json.Path("$.temporary"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_del("profile", "$.temporary")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.GET
Source: https://upstash.com/docs/redis/commands/json/json-get

Use `JSON.GET` to read one or more values from a JSON document.

Without a path the whole document is returned. The reply shape depends on the path syntax: a path starting with `$` is a JSONPath and always returns an array with one entry per match, so an empty array means nothing matched, while the legacy dot syntax returns the value itself and reports an error when the path does not exist. Passing several paths returns an object keyed by the path expressions, which is a cheap way to pull a few unrelated branches of a large document in one call.

`INDENT`, `NEWLINE`, and `SPACE` control the formatting of the returned JSON text, which is otherwise compact. They are meant for human-readable output; leave them out when a program parses the reply.

## Syntax

```redis
JSON.GET <key>
  [INDENT indent]
  [NEWLINE newline]
  [SPACE space]
  [path [path ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `INDENT indent` | No | No | Indentation characters for formatted JSON. |
| `NEWLINE newline` | No | No | Line-separator characters for formatted JSON. |
| `SPACE space` | No | No | Characters placed after JSON separators. |
| `path` | No | Yes | One or more paths; defaults to the root. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string or Null bulk string or null array |
| RESP3 | Bulk string or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.GET profile $.name
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const value = await redis.json.get("key", "$.path.to.somewhere");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().get("profile", "$.name")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.GET", "profile", "$.name");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.get("profile", { path: "$.name" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().get("profile", "$.name")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONGet(context.Background(), "profile", "$.name").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonGet("profile", new redis.clients.jedis.json.Path("$.name"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_get("profile", &["$.name"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.MERGE
Source: https://upstash.com/docs/redis/commands/json/json-merge

Use `JSON.MERGE` to merge a JSON value into a document at a path, following the JSON Merge Patch semantics of RFC 7386.

Objects are merged recursively: keys in the patch replace or create the matching keys in the target, keys set to `null` delete them, and any value that is not an object, arrays included, replaces the target outright instead of being merged element by element. The parent must already exist: merging into a missing child of an existing object creates it, but intermediate levels are not built along the way, and a key that does not exist yet can only be created by merging at the root.

This is the command for partial updates of an object, where [`JSON.SET`](/docs/redis/commands/json/json-set) would replace the whole branch: one call can change a few fields, delete another, and leave the rest of the document untouched.

## Syntax

```redis
JSON.MERGE <key> <path> <value>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | Yes | No | Path to merge into. |
| `value` | Yes | No | Valid JSON value containing the merge patch. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.MERGE profile $ '{"active":true}'
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.json.merge("key", "$.path.to.value", {"new": "value"})
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().merge("profile", "$", {"active": True})
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.MERGE", "profile", "$", "{\"active\":true}");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.merge("profile", "$", { active: true });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().merge("profile", "$", {"active": True})
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONMerge(context.Background(), "profile", "$", `{"active":true}`).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonMerge("profile", new redis.clients.jedis.json.Path("$"), java.util.Map.of("active", true));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("JSON.MERGE");
    command.arg("profile");
    command.arg("$");
    command.arg("{\"active\":true}");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.MGET
Source: https://upstash.com/docs/redis/commands/json/json-mget

Use `JSON.MGET` to read the same path from several JSON documents in one call.

The reply holds one entry per key, in the order requested, containing what the path selected in that document, or null when the key does not exist or the path matches nothing. It replaces one [`JSON.GET`](/docs/redis/commands/json/json-get) per key when you are gathering the same field across many documents, for example the price of every product in a cart.

## Syntax

```redis
JSON.MGET <key> [key ...] <path>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | Yes | One or more JSON document keys. |
| `path` | Yes | No | Path read from every key. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string JSON values or null values, one per key |
| RESP3 | Array of bulk-string JSON values or null values, one per key |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.MGET profile:1 profile:2 $.name
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const values = await redis.json.mget(["key1", "key2"],  "$.path.to.somewhere");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().mget(["profile:1", "profile:2"], "$.name")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.MGET", "profile:1", "profile:2", "$.name");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.mGet(["profile:1", "profile:2"], "$.name");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().mget(["profile:1", "profile:2"], "$.name")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONMGet(context.Background(), "$.name", "profile:1", "profile:2").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonMGet(new redis.clients.jedis.json.Path2("$.name"), "profile:1", "profile:2");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_mget(&["profile:1", "profile:2"], "$.name")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.MSET
Source: https://upstash.com/docs/redis/commands/json/json-mset

Use `JSON.MSET` to set values at paths in several JSON documents in one atomic call.

Each triple gives a key, a path, and a value, and missing keys are created. Either every write is applied or none is, with no other command running in between, which is what makes it the right tool for documents that must stay consistent with each other. The reply is `OK`.

## Syntax

```redis
JSON.MSET <key> <path> <value> [key path value ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key path value` | Yes | Yes | One or more key, path, and valid-JSON-value triples. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.MSET profile:1 $ '{"name":"Ada"}'
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.json.mset([
  { key: "profile:1", path: "$", value: { name: "Ada" } },
]);
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().mset([("profile:1", "$", {"name": "Ada"})])
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.MSET", "profile:1", "$", "{\"name\":\"Ada\"}");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.mSet([{ key: "profile:1", path: "$", value: { name: "Ada" } }]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().mset([("profile:1", "$", {"name": "Ada"})])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONMSet(context.Background(), "profile:1", "$", `{"name":"Ada"}`).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import java.nio.charset.StandardCharsets;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.commands.ProtocolCommand;

ProtocolCommand command = () -> "JSON.MSET".getBytes(StandardCharsets.UTF_8);
try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sendCommand(command, "profile:1", "$", "{\"name\":\"Ada\"}");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("JSON.MSET");
    command.arg("profile:1");
    command.arg("$");
    command.arg("{\"name\":\"Ada\"}");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.NUMINCRBY
Source: https://upstash.com/docs/redis/commands/json/json-numincrby

Use `JSON.NUMINCRBY` to add a number to the numeric values a path selects.

The increment may be negative to count down, and it is applied atomically, so concurrent callers cannot lose an update. The reply is the new value of each match; a match that is not a number returns an error. This is how a counter kept inside a document is updated without reading and rewriting the whole document.

## Syntax

```redis
JSON.NUMINCRBY <key> <path> <value>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | Yes | No | Path selecting numbers. |
| `value` | Yes | No | Numeric amount to add. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string containing a JSON number or array |
| RESP3 | Bulk string containing a JSON number or array |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.NUMINCRBY profile $.visits 1
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const newValue = await redis.json.numincrby("key", "$.path.to.value", 2);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().numincrby("profile", "$.visits", 1)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.NUMINCRBY", "profile", "$.visits", "1");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.numIncrBy("profile", "$.visits", 1);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().numincrby("profile", "$.visits", 1)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONNumIncrBy(context.Background(), "profile", "$.visits", 1).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonNumIncrBy("profile", new redis.clients.jedis.json.Path("$.visits"), 1);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_num_incr_by("profile", "$.visits", 1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.NUMMULTBY
Source: https://upstash.com/docs/redis/commands/json/json-nummultby

Use `JSON.NUMMULTBY` to multiply the numeric values a path selects by a number.

The multiplier may be a fraction to scale values down, and the update is atomic. The reply is the new value of each match, and a match that is not a number returns an error. It is the multiplicative counterpart of [`JSON.NUMINCRBY`](/docs/redis/commands/json/json-numincrby), useful for applying percentage changes such as a discount to every price in a document.

## Syntax

```redis
JSON.NUMMULTBY <key> <path> <value>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | Yes | No | Path selecting numbers. |
| `value` | Yes | No | Numeric multiplier. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string containing a JSON number or array |
| RESP3 | Bulk string containing a JSON number or array |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.NUMMULTBY profile $.score 2
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const newValue = await redis.json.nummultby("key", "$.path.to.value", 2);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().nummultby("profile", "$.score", 2)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.NUMMULTBY", "profile", "$.score", "2");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.numMultBy("profile", "$.score", 2);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().nummultby("profile", "$.score", 2)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Do(context.Background(), "JSON.NUMMULTBY", "profile", "$.score", "2").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import java.nio.charset.StandardCharsets;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.commands.ProtocolCommand;

ProtocolCommand command = () -> "JSON.NUMMULTBY".getBytes(StandardCharsets.UTF_8);
try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sendCommand(command, "profile", "$.score", "2");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("JSON.NUMMULTBY");
    command.arg("profile");
    command.arg("$.score");
    command.arg("2");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.OBJKEYS
Source: https://upstash.com/docs/redis/commands/json/json-objkeys

Use `JSON.OBJKEYS` to list the field names of the objects a path selects.

Without a path the root value is used. The reply holds one list of keys per match, with null for matches that are not objects. Only the field names come back, not the values, which makes it a cheap way to inspect the shape of a document before reading it.

## Syntax

```redis
JSON.OBJKEYS <key> [path]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | No | No | Path selecting objects; defaults to the root. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of arrays of bulk-string object keys or null values, or Null bulk string or null array |
| RESP3 | Array of arrays of bulk-string object keys or null values, or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.OBJKEYS profile $
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const keys = await redis.json.objkeys("key", "$.path");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().objkeys("profile", "$")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.OBJKEYS", "profile", "$");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.objKeys("profile", { path: "$" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().objkeys("profile", "$")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONObjKeys(context.Background(), "profile", "$").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonObjKeys("profile", new redis.clients.jedis.json.Path("$"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_obj_keys("profile", "$")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.OBJLEN
Source: https://upstash.com/docs/redis/commands/json/json-objlen

Use `JSON.OBJLEN` to get the number of fields in the objects a path selects.

Without a path the root value is used. The reply holds one count per match, with null for matches that are not objects. It counts only the object's own fields, not the fields of nested objects.

## Syntax

```redis
JSON.OBJLEN <key> [path]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | No | No | Path selecting objects; defaults to the root. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer, array of integer lengths or null values, or Null bulk string or null array |
| RESP3 | Integer, array of integer lengths or null values, or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.OBJLEN profile $
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const lengths = await redis.json.objlen("key", "$.path");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().objlen("profile", "$")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.OBJLEN", "profile", "$");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.objLen("profile", { path: "$" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().objlen("profile", "$")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONObjLen(context.Background(), "profile", "$").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonObjLen("profile", new redis.clients.jedis.json.Path("$"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_obj_len("profile", "$")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.RESP
Source: https://upstash.com/docs/redis/commands/json/json-resp

Use `JSON.RESP` to get a JSON value in RESP form instead of as JSON text.

The document is translated structurally: an object becomes an array whose first element is `{` followed by alternating field names and values, an array becomes an array whose first element is `[` followed by its elements, and scalars become the corresponding RESP types. Nested values are translated the same way, recursively.

This lets a client walk the structure using the protocol types it already decodes, without running a JSON parser on the reply. For ordinary use, [`JSON.GET`](/docs/redis/commands/json/json-get) is the more convenient command.

## Syntax

```redis
JSON.RESP <key> [path]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | No | No | Path to convert; defaults to the root. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Nested array, Integer, Bulk string, Simple string (`true` or `false` for JSON booleans), or Null bulk string or null array |
| RESP3 | Nested array, Integer, Bulk string, Simple string (`true` or `false` for JSON booleans), or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.RESP profile $
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.json.resp("profile", "$");
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().resp("profile", "$")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.RESP", "profile", "$");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sendCommand(["JSON.RESP", "profile", "$"]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().resp("profile", "$")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Do(context.Background(), "JSON.RESP", "profile", "$").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import java.nio.charset.StandardCharsets;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.commands.ProtocolCommand;

ProtocolCommand command = () -> "JSON.RESP".getBytes(StandardCharsets.UTF_8);
try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sendCommand(command, "profile", "$");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("JSON.RESP");
    command.arg("profile");
    command.arg("$");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.SET
Source: https://upstash.com/docs/redis/commands/json/json-set

Use `JSON.SET` to set a JSON value at a path inside a document, creating the key when it does not exist.

The value is JSON text; the Upstash SDK helpers serialize native objects for you. With the root path (`$`) the whole document is replaced, which is also how a new document is created. For a nested path the parent must already exist: the command adds one missing child to an existing object or appends to an existing array, but it does not create intermediate levels along the way.

`NX` writes only when the path does not exist yet and `XX` only when it does, which makes conditional updates atomic. When the path is a JSONPath that matches several places, every match is updated in the same call, so a single command can update all elements of an array.

## Syntax

```redis
JSON.SET <key> <path> <value> [NX | XX]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | Yes | No | Path to create or replace. |
| `value` | Yes | No | Valid JSON value. |
| `NX \| XX` | No | No | Write only when the path is absent (`NX`) or present (`XX`). |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK`, or Null bulk string or null array when `NX` or `XX` prevented the write |
| RESP3 | Simple string `OK`, or Null when `NX` or `XX` prevented the write |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.SET profile $ '{"name":"Ada"}'
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

redis.json.set(key, "$.path", value);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().set("profile", "$", {"name": "Ada"})
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.SET", "profile", "$", "{\"name\":\"Ada\"}");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.set("profile", "$", { name: "Ada" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().set("profile", "$", {"name": "Ada"})
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONSet(context.Background(), "profile", "$", map[string]interface{}{"name": "Ada"}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonSet("profile", new redis.clients.jedis.json.Path("$"), java.util.Map.of("name", "Ada"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_set("profile", "$", &serde_json::json!({"name": "Ada"}))?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.STRAPPEND
Source: https://upstash.com/docs/redis/commands/json/json-strappend

Use `JSON.STRAPPEND` to append text to the string values a path selects.

The value is JSON text, so the appended string must be quoted, as in `'"suffix"'`. The reply is the new length of each string the path matched, with an error for matches that are not strings. Appending in place avoids reading and rewriting the whole document just to extend one field.

## Syntax

```redis
JSON.STRAPPEND <key> <path> <value>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | Yes | No | Path selecting strings. |
| `value` | Yes | No | JSON-encoded string to append. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer, array of integer lengths or null values, or Null bulk string or null array |
| RESP3 | Integer, array of integer lengths or null values, or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.STRAPPEND profile $.name '" Lovelace"'
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.json.strappend("key", "$.path.to.str", "abc");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().strappend("profile", "$.name", " Lovelace")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.STRAPPEND", "profile", "$.name", "\" Lovelace\"");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.strAppend("profile", " Lovelace", { path: "$.name" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().strappend("profile", " Lovelace", "$.name")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONStrAppend(context.Background(), "profile", "$.name", `" Lovelace"`).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonStrAppend("profile", new redis.clients.jedis.json.Path("$.name"), " Lovelace");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_str_append("profile", "$.name", " Lovelace")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.STRLEN
Source: https://upstash.com/docs/redis/commands/json/json-strlen

Use `JSON.STRLEN` to get the length of the string values a path selects.

Without a path the root value is used. The reply holds one length per match, with null for matches that are not strings, so it is also a quick way to check that a field is a string. The value itself is not transferred, which makes it cheap even for long strings.

## Syntax

```redis
JSON.STRLEN <key> [path]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | No | No | Path selecting strings; defaults to the root. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer, array of integer lengths or null values, or Null bulk string or null array |
| RESP3 | Integer, array of integer lengths or null values, or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.STRLEN profile $.name
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.json.strlen("key", "$.path.to.str", "a");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().strlen("profile", "$.name")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.STRLEN", "profile", "$.name");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.strLen("profile", { path: "$.name" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().strlen("profile", "$.name")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONStrLen(context.Background(), "profile", "$.name").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonStrLen("profile", new redis.clients.jedis.json.Path("$.name"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_str_len("profile", "$.name")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.TOGGLE
Source: https://upstash.com/docs/redis/commands/json/json-toggle

Use `JSON.TOGGLE` to flip the boolean values a path selects, turning `true` into `false` and back.

The reply is the new value of each match, and a match that is not a boolean returns an error. Because the read and the write are one atomic step, this is the safe way to flip a flag inside a document, where reading it and writing the opposite value back would race with other clients.

## Syntax

```redis
JSON.TOGGLE <key> <path>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | Yes | No | Path selecting Boolean values. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integer Boolean values or null values, one per matched path |
| RESP3 | Array of integer Boolean values or null values, one per matched path |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.TOGGLE profile $.active
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const bool = await redis.json.toggle("key", "$.path.to.bool");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().toggle("profile", "$.active")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.TOGGLE", "profile", "$.active");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.toggle("profile", "$.active");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().toggle("profile", "$.active")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONToggle(context.Background(), "profile", "$.active").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonToggle("profile", new redis.clients.jedis.json.Path("$.active"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_toggle("profile", "$.active")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON.TYPE
Source: https://upstash.com/docs/redis/commands/json/json-type

Use `JSON.TYPE` to find out the JSON type of the values a path selects.

The reply names one type per match, one of `object`, `array`, `string`, `integer`, `number`, `boolean`, or `null`, and is empty when the path matches nothing. Whole numbers report as `integer` and fractional ones as `number`. It is the way to inspect documents whose shape you do not control before applying type-specific commands, which would otherwise fail.

## Syntax

```redis
JSON.TYPE <key> [path]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | JSON document key. |
| `path` | No | No | Path to inspect; defaults to the root. |

## Important points

* Raw TCP examples pass JSON values as valid JSON text. Typed Upstash SDK helpers serialize native objects and values for you.
* Paths beginning with `$` use JSONPath and can match multiple values, so many JSON commands return an array of per-match results.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string, array of bulk-string type names, or Null bulk string or null array |
| RESP3 | Bulk string, array of bulk-string type names, or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
JSON.TYPE profile $.name
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const myType = await redis.json.type("key", "$.path.to.value");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.json().type("profile", "$.name")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("JSON.TYPE", "profile", "$.name");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.json.type("profile", { path: "$.name" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.json().type("profile", "$.name")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.JSONType(context.Background(), "profile", "$.name").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.JedisPooled;

try (JedisPooled jedis = new JedisPooled(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.jsonType("profile", new redis.clients.jedis.json.Path("$.name"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::JsonCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: redis::Value = connection.json_type("profile", "$.name")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# JSON commands
Source: https://upstash.com/docs/redis/commands/json/overview

To query inside JSON values (full-text, fuzzy, phrase, regex), see [Upstash Redis Search](/docs/redis/search/introduction).

<CardGroup cols={2}>
<Card title="JSON.ARRAPPEND" href="/redis/commands/json/json-arrappend">Append values to JSON array</Card>
<Card title="JSON.ARRINDEX" href="/redis/commands/json/json-arrindex">Find index of value in array</Card>
<Card title="JSON.ARRINSERT" href="/redis/commands/json/json-arrinsert">Insert values into JSON array</Card>
<Card title="JSON.ARRLEN" href="/redis/commands/json/json-arrlen">Get JSON array length</Card>
<Card title="JSON.ARRPOP" href="/redis/commands/json/json-arrpop">Pop value from JSON array</Card>
<Card title="JSON.ARRTRIM" href="/redis/commands/json/json-arrtrim">Trim JSON array to range</Card>
<Card title="JSON.CLEAR" href="/redis/commands/json/json-clear">Clear JSON values</Card>
<Card title="JSON.DEL" href="/redis/commands/json/json-del">Delete JSON values</Card>
<Card title="JSON.DEBUG" href="/redis/commands/json/json-debug">Inspect JSON memory usage</Card>
<Card title="JSON.FORGET" href="/redis/commands/json/json-forget">Delete JSON values (alias of JSON.DEL)</Card>
<Card title="JSON.GET" href="/redis/commands/json/json-get">Get JSON values</Card>
<Card title="JSON.MERGE" href="/redis/commands/json/json-merge">Merge JSON values</Card>
<Card title="JSON.MGET" href="/redis/commands/json/json-mget">Get values from multiple keys</Card>
<Card title="JSON.MSET" href="/redis/commands/json/json-mset">Set values in multiple keys</Card>
<Card title="JSON.NUMINCRBY" href="/redis/commands/json/json-numincrby">Increment JSON number</Card>
<Card title="JSON.NUMMULTBY" href="/redis/commands/json/json-nummultby">Multiply JSON number</Card>
<Card title="JSON.OBJKEYS" href="/redis/commands/json/json-objkeys">Get JSON object keys</Card>
<Card title="JSON.OBJLEN" href="/redis/commands/json/json-objlen">Get JSON object size</Card>
<Card title="JSON.RESP" href="/redis/commands/json/json-resp">Get JSON in RESP format</Card>
<Card title="JSON.SET" href="/redis/commands/json/json-set">Set JSON value</Card>
<Card title="JSON.STRAPPEND" href="/redis/commands/json/json-strappend">Append to JSON string</Card>
<Card title="JSON.STRLEN" href="/redis/commands/json/json-strlen">Get JSON string length</Card>
<Card title="JSON.TOGGLE" href="/redis/commands/json/json-toggle">Toggle JSON boolean</Card>
<Card title="JSON.TYPE" href="/redis/commands/json/json-type">Get JSON value type</Card>
</CardGroup>

# BLMOVE
Source: https://upstash.com/docs/redis/commands/list/blmove

Use `BLMOVE` to move an element from one list to another, blocking until the source has an element or the timeout expires.

It is the blocking form of [`LMOVE`](/docs/redis/commands/list/lmove): when the source list is not empty it behaves identically and returns immediately, and when it is empty the connection waits instead of returning null. The timeout is given in seconds, may be fractional, and `0` waits indefinitely. If several clients are waiting on the same key, the one that has been waiting longest is served first.

Because the element is never outside a list, this is the standard way to build a reliable queue: a worker blocks until work appears, atomically moves it to a processing list, and deletes it from there when done, so an interrupted job can be recovered instead of lost.

## Syntax

```redis
BLMOVE <source> <destination> (LEFT | RIGHT) (LEFT | RIGHT) <timeout>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<source>` | Yes | No | Redis key used as source. |
| `<destination>` | Yes | No | Redis key used as destination. |
| `(LEFT \| RIGHT)` | Yes | No | Which end of the source list the element is taken from: `LEFT` (head) or `RIGHT` (tail). |
| `(LEFT \| RIGHT)` | Yes | No | Which end of the destination list the element is pushed onto: `LEFT` (head) or `RIGHT` (tail). |
| `<timeout>` | Yes | No | Seconds to block; `0` blocks indefinitely. |

## Important points

* A blocking form holds the request until data arrives or its timeout expires. Set the client/network timeout longer than the command timeout.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string or Null bulk string or null array |
| RESP3 | Bulk string or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
BLMOVE source-key destination-key LEFT LEFT 1.5
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.blmove("source-key", "destination-key", "LEFT", "LEFT", "1.5");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.blMove("source-key", "destination-key", "LEFT", "LEFT", 1.5);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.blmove("source-key", "destination-key", 1.5, "LEFT", "LEFT")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.BLMove(context.Background(), "source-key", "destination-key", "LEFT", "LEFT", 1500*time.Millisecond).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.blmove("source-key", "destination-key", redis.clients.jedis.args.ListDirection.LEFT, redis.clients.jedis.args.ListDirection.LEFT, 1.5);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::{Direction, TypedCommands};

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.blmove(
        "source-key",
        "destination-key",
        Direction::Left,
        Direction::Left,
        1.5,
    )?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# BLMPOP
Source: https://upstash.com/docs/redis/commands/list/blmpop

Use `BLMPOP` to pop elements from the first non-empty list among several, blocking until one has elements or the timeout expires.

It is the blocking form of [`LMPOP`](/docs/redis/commands/list/lmpop): keys are examined in the order given, so listing a high priority queue first drains it before the others are considered, `LEFT` or `RIGHT` chooses the end, and `COUNT` sets how many elements to take. The reply names the key that was popped from along with the elements.

The timeout is in seconds, may be fractional, and `0` waits indefinitely; when it expires the reply is null. This is the command to reach for when one worker serves several queues of differing priority.

## Syntax

```redis
BLMPOP <timeout> <numkeys> <key> [<key> ...] (LEFT | RIGHT) [COUNT <count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<timeout>` | Yes | No | Seconds to block; `0` blocks indefinitely. |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |
| `(LEFT \| RIGHT)` | Yes | No | Which end to pop from: `LEFT` (head) or `RIGHT` (tail). |
| `COUNT <count>` | No | No | Maximum number of elements to pop. |

## Important points

* A blocking form holds the request until data arrives or its timeout expires. Set the client/network timeout longer than the command timeout.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, or two-element array: key and array of values |
| RESP3 | Null, or two-element array: key and array of values |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
BLMPOP 1.5 1 my-key LEFT
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.blmpop("1.5", "1", "my-key", "LEFT");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.blmPop(1.5, "my-key", "LEFT");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.blmpop(1.5, 1, "my-key", direction="LEFT")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    _, result, err := client.BLMPop(context.Background(), 1500*time.Millisecond, "LEFT", 0, "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.blmpop(1.5, redis.clients.jedis.args.ListDirection.LEFT, "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::{Direction, TypedCommands};

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.blmpop(1.5, 1, "my-key", Direction::Left, 1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# BLPOP
Source: https://upstash.com/docs/redis/commands/list/blpop

Use `BLPOP` to pop an element from the head of the first non-empty list, blocking until one has an element or the timeout expires.

It is the blocking form of [`LPOP`](/docs/redis/commands/list/lpop) and it accepts several keys, which are checked in the order given, so earlier keys act as higher priority queues. The reply names the key the element came from together with the element itself, which matters when you are waiting on more than one queue.

The timeout is in seconds, may be fractional, and `0` waits indefinitely; when it expires the reply is null. Blocking lets a worker wait for work without polling, which cuts both latency and wasted commands. When several clients are blocked on the same key they are served in the order they started waiting.

## Syntax

```redis
BLPOP <key> [<key> ...] <timeout>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | Yes | Redis key targeted by the command. |
| `<timeout>` | Yes | No | Seconds to block; `0` blocks indefinitely. |

## Important points

* A blocking form holds the request until data arrives or its timeout expires. Set the client/network timeout longer than the command timeout.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, or two-element key/value array |
| RESP3 | Null, or two-element key/value array |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
BLPOP my-key 1.5
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.blpop("my-key", "1.5");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.blPop("my-key", 1.5);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.blpop(["my-key"], timeout=1.5)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.BLPop(context.Background(), 1500*time.Millisecond, "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.blpop(1.5, "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.blpop("my-key", 1.5)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# BRPOP
Source: https://upstash.com/docs/redis/commands/list/brpop

Use `BRPOP` to pop an element from the tail of the first non-empty list, blocking until one has an element or the timeout expires.

It is the blocking form of [`RPOP`](/docs/redis/commands/list/rpop) and behaves like [`BLPOP`](/docs/redis/commands/list/blpop) in every other respect: several keys are checked in the order given, the reply names the key the element came from, the timeout is in seconds and may be fractional with `0` meaning wait forever, and clients blocked on the same key are served in the order they started waiting.

Producers pushing with [`LPUSH`](/docs/redis/commands/list/lpush) and consumers waiting with `BRPOP` form the classic first-in, first-out worker queue.

## Syntax

```redis
BRPOP <key> [<key> ...] <timeout>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | Yes | Redis key targeted by the command. |
| `<timeout>` | Yes | No | Seconds to block; `0` blocks indefinitely. |

## Important points

* A blocking form holds the request until data arrives or its timeout expires. Set the client/network timeout longer than the command timeout.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, or two-element key/value array |
| RESP3 | Null, or two-element key/value array |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
BRPOP my-key 1.5
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.brpop("my-key", "1.5");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.brPop("my-key", 1.5);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.brpop(["my-key"], timeout=1.5)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.BRPop(context.Background(), 1500*time.Millisecond, "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.brpop(1.5, "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.brpop("my-key", 1.5)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# BRPOPLPUSH
Source: https://upstash.com/docs/redis/commands/list/brpoplpush

<Warning>
  Prefer [`BLMOVE`](/docs/redis/commands/list/blmove) with `RIGHT` and `LEFT` in new code: `BLMOVE <source> <destination> RIGHT LEFT <timeout>`.
</Warning>

Use `BRPOPLPUSH` to pop an element from the tail of one list and push it to the head of another, blocking until the source has an element or the timeout expires.

It is the blocking form of [`RPOPLPUSH`](/docs/redis/commands/list/rpoplpush). The timeout is in seconds, may be fractional, and `0` waits indefinitely; when it expires the reply is null. Since the element moves atomically into the destination, a worker that crashes after taking an item leaves it visible in the processing list, where it can be recovered.

[`BLMOVE`](/docs/redis/commands/list/blmove) does the same thing and additionally lets you choose which end of each list to use.

## Syntax

```redis
BRPOPLPUSH <source> <destination> <timeout>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<source>` | Yes | No | Redis key used as source. |
| `<destination>` | Yes | No | Redis key used as destination. |
| `<timeout>` | Yes | No | Seconds to block; `0` blocks indefinitely. |

## Important points

* A blocking form holds the request until data arrives or its timeout expires. Set the client/network timeout longer than the command timeout.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string or Null bulk string or null array |
| RESP3 | Bulk string or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
BRPOPLPUSH source-key destination-key 1.5
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.brpoplpush("source-key", "destination-key", "1.5");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.brPopLPush("source-key", "destination-key", 1.5);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.brpoplpush("source-key", "destination-key", timeout=1.5)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.BRPopLPush(context.Background(), "source-key", "destination-key", 1500*time.Millisecond).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.brpoplpush("source-key", "destination-key", 1);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.brpoplpush("source-key", "destination-key", 1.5)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# LINDEX
Source: https://upstash.com/docs/redis/commands/list/lindex

Use `LINDEX` to read the element at a given position in a list.

Indexes are zero-based from the head, and negative indexes count from the tail, so `-1` is the last element. The reply is null when the key does not exist or the index is out of range. Redis walks the list from the nearer end to reach the index, so access is fast near the head and tail and gets more expensive towards the middle of a long list.

## Syntax

```redis
LINDEX <key> <index>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<index>` | Yes | No | Zero-based index; negative values count from the end. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array or Bulk string |
| RESP3 | Null or Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
LINDEX my-key 0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.rpush("key", "a", "b", "c");
const element = await redis.lindex("key", 0);
console.log(element); // "a"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.lindex("my-key", 0)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.lindex("my-key", "0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.lIndex("my-key", 0);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.lindex("my-key", 0)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.LIndex(context.Background(), "my-key", 0).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.lindex("my-key", 0);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.lindex("my-key", 0)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# LINSERT
Source: https://upstash.com/docs/redis/commands/list/linsert

Use `LINSERT` to insert an element immediately before or after another element of a list.

The pivot is matched by value, and only its first occurrence starting from the head is used. The reply is the new length of the list, `0` when the key does not exist, and `-1` when the pivot value was not found, which is how you tell a failed insert from a successful one.

Finding the pivot means scanning the list, so this is a linear operation; on long lists it is worth keeping an index elsewhere or using a sorted set instead.

## Syntax

```redis
LINSERT <key> (BEFORE | AFTER) <pivot> <element>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `(BEFORE \| AFTER)` | Yes | No | Where to place the new element relative to the pivot: `BEFORE` or `AFTER`. |
| `<pivot>` | Yes | No | Existing element to insert next to. |
| `<element>` | Yes | No | Element to insert. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: the list length after insertion, `-1` if the pivot was not found, `0` if the key does not exist |
| RESP3 | Integer: the list length after insertion, `-1` if the pivot was not found, `0` if the key does not exist |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
LINSERT my-key BEFORE pivot element
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.rpush("key", "a", "b", "c");
await redis.linsert("key", "before", "b", "x");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.linsert("my-key", "BEFORE", "pivot", "element")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.linsert("my-key", "BEFORE", "pivot", "element");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.lInsert("my-key", "BEFORE", "pivot", "element");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.linsert("my-key", "BEFORE", "pivot", "element")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.LInsertBefore(context.Background(), "my-key", "pivot", "element").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.linsert("my-key", redis.clients.jedis.args.ListPosition.BEFORE, "pivot", "element");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.linsert_before("my-key", "pivot", "element")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# LLEN
Source: https://upstash.com/docs/redis/commands/list/llen

Use `LLEN` to get the number of elements in a list.

The reply is `0` when the key does not exist. The length is maintained by Redis rather than computed on demand, so the command is cheap whatever the size of the list, which makes it the usual way to monitor a queue's backlog.

## Syntax

```redis
LLEN <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
LLEN my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.rpush("key", "a", "b", "c");
const length = await redis.llen("key");
console.log(length); // 3
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.llen("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.llen("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.lLen("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.llen("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.LLen(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.llen("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.llen("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# LMOVE
Source: https://upstash.com/docs/redis/commands/list/lmove

Use `LMOVE` to atomically take an element from one end of a list and push it onto one end of another list, returning the element.

The two directions are chosen independently: `LEFT RIGHT` takes from the head of the source and appends to the tail of the destination, which preserves order when transferring between queues, while `LEFT LEFT` behaves like moving between stacks. If the source is empty nothing happens and the reply is null. Source and destination may be the same key, in which case the list is rotated.

Because the element is never outside a list, `LMOVE` is the building block for reliable queues: a worker moves an item into a processing list, does the work, and removes it from there, so a crash leaves the item recoverable instead of lost. It replaces the deprecated [`RPOPLPUSH`](/docs/redis/commands/list/rpoplpush), and [`BLMOVE`](/docs/redis/commands/list/blmove) is the blocking form.

## Syntax

```redis
LMOVE <source> <destination> (LEFT | RIGHT) (LEFT | RIGHT)
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<source>` | Yes | No | Redis key used as source. |
| `<destination>` | Yes | No | Redis key used as destination. |
| `(LEFT \| RIGHT)` | Yes | No | Which end of the source list the element is taken from: `LEFT` (head) or `RIGHT` (tail). |
| `(LEFT \| RIGHT)` | Yes | No | Which end of the destination list the element is pushed onto: `LEFT` (head) or `RIGHT` (tail). |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string |
| RESP3 | Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
LMOVE source-key destination-key LEFT LEFT
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.rpush("source", "a", "b", "c");
const element = await redis.lmove("source", "destination", "left", "left");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.lmove("source-key", "destination-key", "LEFT", "LEFT")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.lmove("source-key", "destination-key", "LEFT", "LEFT");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.lMove("source-key", "destination-key", "LEFT", "LEFT");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.lmove("source-key", "destination-key", "LEFT", "LEFT")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.LMove(context.Background(), "source-key", "destination-key", "LEFT", "LEFT").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.lmove("source-key", "destination-key", redis.clients.jedis.args.ListDirection.LEFT, redis.clients.jedis.args.ListDirection.LEFT);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.lmove("source-key", "destination-key", redis::Direction::Left, redis::Direction::Left)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# LMPOP
Source: https://upstash.com/docs/redis/commands/list/lmpop

Use `LMPOP` to pop elements from the first of several lists that is not empty.

`<numkeys>` states how many keys follow, `LEFT` or `RIGHT` chooses the end to pop from, and `COUNT` sets how many elements to take, defaulting to one. Keys are examined in the order given and only the first non-empty one is touched, which is exactly what a priority queue needs: list the high priority queue first and it is drained before the others are looked at.

The reply names the key that was popped from together with the elements, so a caller working with several queues knows where the work came from. When every key is empty the reply is null; use [`BLMPOP`](/docs/redis/commands/list/blmpop) to wait instead.

## Syntax

```redis
LMPOP <numkeys> <key> [<key> ...] (LEFT | RIGHT) [COUNT <count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |
| `(LEFT \| RIGHT)` | Yes | No | Which end to pop from: `LEFT` (head) or `RIGHT` (tail). |
| `COUNT <count>` | No | No | Maximum number of elements to pop. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, or two-element array: key and array of values |
| RESP3 | Null, or two-element array: key and array of values |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
LMPOP 1 my-key LEFT
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.lmpop(1, ["my-key"], "LEFT");
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.lmpop("1", "my-key", "LEFT");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.lmPop("my-key", "LEFT");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.lmpop(1, "my-key", direction="LEFT")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    _, result, err := client.LMPop(context.Background(), "LEFT", 0, "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.lmpop(redis.clients.jedis.args.ListDirection.LEFT, "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::{Direction, TypedCommands};

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.lmpop(1, "my-key", Direction::Left, 1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# LPOP
Source: https://upstash.com/docs/redis/commands/list/lpop

Use `LPOP` to remove and return elements from the head of a list.

Without a count a single element is returned, or null when the key does not exist. With a count, up to that many elements are removed and returned in the order they were popped, and the reply is an empty array or null when the list is empty. The key is deleted once the last element is removed.

Paired with [`RPUSH`](/docs/redis/commands/list/rpush) this gives a first-in, first-out queue; paired with [`LPUSH`](/docs/redis/commands/list/lpush) it gives a stack. Use [`BLPOP`](/docs/redis/commands/list/blpop) when a consumer should wait for work rather than poll.

## Syntax

```redis
LPOP <key> [<count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<count>` | No | No | Number of elements to pop. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, Bulk string, or array of bulk-string values |
| RESP3 | Null, Bulk string, or array of bulk-string values |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
LPOP my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.rpush("key", "a", "b", "c");
const element = await redis.lpop("key");
console.log(element); // "a"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.lpop("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.lpop("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.lPop("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.lpop("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.LPop(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.lpop("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: Option<String> = connection.lpop("my-key", None)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# LPOS
Source: https://upstash.com/docs/redis/commands/list/lpos

Use `LPOS` to find the position of an element in a list.

The scan starts at the head and, by default, reports the index of the first match or null when there is none. `RANK` selects which match to report: `RANK 2` skips to the second occurrence, and a negative rank searches backwards from the tail, so `RANK -1` finds the last occurrence. `COUNT` returns that many matching indexes instead of just one, and `COUNT 0` returns all of them. `MAXLEN` limits how many elements are compared, which bounds the cost of the search on a long list at the price of possibly missing matches beyond that point.

It is the read-only way to locate a value before acting on it with [`LSET`](/docs/redis/commands/list/lset) or [`LREM`](/docs/redis/commands/list/lrem).

## Syntax

```redis
LPOS <key> <element> [RANK <rank>] [COUNT <num-matches>] [MAXLEN <len>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<element>` | Yes | No | Element value to search for. |
| `RANK <rank>` | No | No | Which match to return; negative values search from the tail. |
| `COUNT <num-matches>` | No | No | Number of matches to return; `0` returns every match. |
| `MAXLEN <len>` | No | No | Maximum number of entries to keep in the stream. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, Integer, or array of integer positions |
| RESP3 | Null, Integer, or array of integer positions |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
LPOS my-key element
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.rpush("key", "a", "b", "c");
const index = await redis.lpos("key", "b");
console.log(index); // 1
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.lpos("my-key", "element")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.lpos("my-key", "element");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.lPos("my-key", "element");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.lpos("my-key", "element")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.LPos(context.Background(), "my-key", "element", redis.LPosArgs{}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.lpos("my-key", "element");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::{LposOptions, TypedCommands};

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: Option<isize> = connection.lpos("my-key", "element", LposOptions::default())?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# LPUSH
Source: https://upstash.com/docs/redis/commands/list/lpush

Use `LPUSH` to add one or more elements to the head of a list, creating the list when the key does not exist.

Elements are inserted one after another, so they end up in reverse order relative to the argument list: `LPUSH key a b c` leaves the list as `c`, `b`, `a`. The reply is the length of the list after the push. If the key holds a value of another type the command returns an error.

Pushing to the head and popping from the tail with [`RPOP`](/docs/redis/commands/list/rpop) gives a first-in, first-out queue, while popping from the head with [`LPOP`](/docs/redis/commands/list/lpop) gives a stack. Combine with [`LTRIM`](/docs/redis/commands/list/ltrim) to keep a capped list of recent items.

## Syntax

```redis
LPUSH <key> <element> [<element> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<element>` | Yes | Yes | Element to push. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
LPUSH my-key element
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const length1 = await redis.lpush("key", "a", "b", "c");
console.log(length1); // 3
const length2 = await redis.lpush("key", "d");
console.log(length2); // 4
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.lpush("my-key", "element")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.lpush("my-key", "element");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.lPush("my-key", "element");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.lpush("my-key", "element")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.LPush(context.Background(), "my-key", "element").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.lpush("my-key", "element");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.lpush("my-key", "element")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# LPUSHX
Source: https://upstash.com/docs/redis/commands/list/lpushx

Use `LPUSHX` to add elements to the head of a list only when the list already exists.

Nothing happens and the reply is `0` when the key does not exist, and no key is created. This is the difference from [`LPUSH`](/docs/redis/commands/list/lpush), and it is what you want when a list should only be fed while a consumer is holding it open, so that stale producers do not resurrect a queue that was already drained and deleted.

## Syntax

```redis
LPUSHX <key> <element> [<element> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<element>` | Yes | Yes | Element to push. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
LPUSHX my-key element
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.lpush("key", "a", "b", "c");
const length = await redis.lpushx("key", "d");
console.log(length); // 4
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.lpushx("my-key", "element")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.lpushx("my-key", "element");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.lPushX("my-key", "element");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.lpushx("my-key", "element")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.LPushX(context.Background(), "my-key", "element").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.lpushx("my-key", "element");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.lpush_exists("my-key", "element")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# LRANGE
Source: https://upstash.com/docs/redis/commands/list/lrange

Use `LRANGE` to read a range of elements from a list.

Both `<start>` and `<stop>` are zero-based, inclusive, and may be negative to count from the tail, so `LRANGE key 0 -1` returns the whole list and `LRANGE key 0 9` returns the first ten elements. Out-of-range indexes are clamped instead of producing an error, and a range that selects nothing, or a missing key, returns an empty list.

The command copies the requested range into the reply, so reading a large list in one call is expensive: page through it with successive ranges when the list is big.

## Syntax

```redis
LRANGE <key> <start> <stop>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<start>` | Yes | No | Start index; negative values count from the end. |
| `<stop>` | Yes | No | Stop index, inclusive; negative values count from the end. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string values |
| RESP3 | Array of bulk-string values |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
LRANGE my-key 0 0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.lpush("key", "a", "b", "c");
const elements = await redis.lrange("key", 1, 2);
console.log(elements) // ["b", "c"]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.lrange("my-key", 0, 0)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.lrange("my-key", "0", "0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.lRange("my-key", 0, 0);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.lrange("my-key", 0, 0)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.LRange(context.Background(), "my-key", 0, 0).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.lrange("my-key", 0, 0);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.lrange("my-key", 0, 0)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# LREM
Source: https://upstash.com/docs/redis/commands/list/lrem

Use `LREM` to remove elements equal to a given value from a list.

The count decides how many occurrences are removed and in which direction: a positive count removes that many starting from the head, a negative count removes that many starting from the tail, and `0` removes every occurrence. The reply is the number of elements actually removed, and the key is deleted when the list becomes empty.

Removing by value means scanning the list, so on long lists prefer a set or a sorted set when you frequently need to delete arbitrary items.

## Syntax

```redis
LREM <key> <count> <element>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<count>` | Yes | No | How many matches to remove: positive scans head to tail, negative tail to head, `0` removes every match. |
| `<element>` | Yes | No | Element value to remove. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
LREM my-key 1 element
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.lpush("key", "a", "a", "b", "b", "c");
const removed = await redis.lrem("key", 4, "b");
console.log(removed) // 2
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.lrem("my-key", 1, "element")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.lrem("my-key", "1", "element");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.lRem("my-key", 1, "element");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.lrem("my-key", 1, "element")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.LRem(context.Background(), "my-key", 1, "element").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.lrem("my-key", 1, "element");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.lrem("my-key", 1, "element")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# LSET
Source: https://upstash.com/docs/redis/commands/list/lset

Use `LSET` to overwrite the element at a given position in a list.

Indexes are zero-based from the head and negative indexes count from the tail. The command returns an error when the key does not exist or the index is out of range, since it never grows the list; use [`LPUSH`](/docs/redis/commands/list/lpush) or [`RPUSH`](/docs/redis/commands/list/rpush) to add elements. Locate the position first with [`LPOS`](/docs/redis/commands/list/lpos) when you know the value but not the index.

## Syntax

```redis
LSET <key> <index> <element>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<index>` | Yes | No | Zero-based index; negative values count from the end. |
| `<element>` | Yes | No | Element to store at the index. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
LSET my-key 0 element
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.lpush("key", "a", "b", "c");
await redis.lset("key", 1, "d");

// list is now ["a", "d", "c"]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.lset("my-key", 0, "element")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.lset("my-key", "0", "element");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.lSet("my-key", 0, "element");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.lset("my-key", 0, "element")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.LSet(context.Background(), "my-key", 0, "element").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.lset("my-key", 0, "element");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.lset("my-key", 0, "element")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# LTRIM
Source: https://upstash.com/docs/redis/commands/list/ltrim

Use `LTRIM` to keep only a range of elements in a list and delete everything outside it.

Both ends are inclusive, zero-based, and may be negative to count from the tail. If `<start>` is greater than `<stop>` or lies past the end of the list, every element is removed and the key is deleted. The reply is always `OK`.

The classic use is a capped list: [`LPUSH`](/docs/redis/commands/list/lpush) a new item and then `LTRIM key 0 99` to keep the hundred most recent ones, which bounds memory without any separate cleanup job.

## Syntax

```redis
LTRIM <key> <start> <stop>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<start>` | Yes | No | Start index; negative values count from the end. |
| `<stop>` | Yes | No | Stop index, inclusive; negative values count from the end. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
LTRIM my-key 0 0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.lpush("key", "a", "b", "c", "d");
await redis.ltrim("key", 1, 2);
// the list is now ["b", "c"]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.ltrim("my-key", 0, 0)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.ltrim("my-key", "0", "0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.lTrim("my-key", 0, 0);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.ltrim("my-key", 0, 0)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.LTrim(context.Background(), "my-key", 0, 0).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.ltrim("my-key", 0, 0);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.ltrim("my-key", 0, 0)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# List commands
Source: https://upstash.com/docs/redis/commands/list/overview

<CardGroup cols={2}>
<Card title="BLMOVE" href="/redis/commands/list/blmove">Blocking list move</Card>
<Card title="BLMPOP" href="/redis/commands/list/blmpop">Blocking pop from multiple lists</Card>
<Card title="BLPOP" href="/redis/commands/list/blpop">Blocking left pop</Card>
<Card title="BRPOP" href="/redis/commands/list/brpop">Blocking right pop</Card>
<Card title="BRPOPLPUSH" href="/redis/commands/list/brpoplpush">Blocking pop and push</Card>
<Card title="LINDEX" href="/redis/commands/list/lindex">Get element by index</Card>
<Card title="LINSERT" href="/redis/commands/list/linsert">Insert before or after pivot</Card>
<Card title="LLEN" href="/redis/commands/list/llen">Get list length</Card>
<Card title="LMOVE" href="/redis/commands/list/lmove">Move element between lists</Card>
<Card title="LMPOP" href="/redis/commands/list/lmpop">Pop from the first non-empty list</Card>
<Card title="LPOP" href="/redis/commands/list/lpop">Pop from list head</Card>
<Card title="LPOS" href="/redis/commands/list/lpos">Find the position of an element</Card>
<Card title="LPUSH" href="/redis/commands/list/lpush">Push to list head</Card>
<Card title="LPUSHX" href="/redis/commands/list/lpushx">Push to head if list exists</Card>
<Card title="LRANGE" href="/redis/commands/list/lrange">Get range of elements</Card>
<Card title="LREM" href="/redis/commands/list/lrem">Remove elements by value</Card>
<Card title="LSET" href="/redis/commands/list/lset">Set element at index</Card>
<Card title="LTRIM" href="/redis/commands/list/ltrim">Trim list to range</Card>
<Card title="RPOP" href="/redis/commands/list/rpop">Pop from list tail</Card>
<Card title="RPOPLPUSH" href="/redis/commands/list/rpoplpush">Pop from tail and push to head</Card>
<Card title="RPUSH" href="/redis/commands/list/rpush">Push to list tail</Card>
<Card title="RPUSHX" href="/redis/commands/list/rpushx">Push to tail if list exists</Card>
</CardGroup>

# RPOP
Source: https://upstash.com/docs/redis/commands/list/rpop

Use `RPOP` to remove and return elements from the tail of a list.

Without a count a single element is returned, or null when the key does not exist. With a count, up to that many elements are removed and returned in the order they were popped, and the key is deleted once the last element is gone.

Combined with [`LPUSH`](/docs/redis/commands/list/lpush) this gives a first-in, first-out queue, since producers add at the head and consumers take from the tail. Use [`BRPOP`](/docs/redis/commands/list/brpop) when a consumer should wait for work instead of polling.

## Syntax

```redis
RPOP <key> [<count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<count>` | No | No | Number of elements to pop. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, Bulk string, or array of bulk-string values |
| RESP3 | Null, Bulk string, or array of bulk-string values |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
RPOP my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.rpush("key", "a", "b", "c");
const element = await redis.rpop("key");
console.log(element); // "c"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.rpop("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.rpop("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.rPop("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.rpop("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.RPop(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.rpop("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: Option<String> = connection.rpop("my-key", None)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# RPOPLPUSH
Source: https://upstash.com/docs/redis/commands/list/rpoplpush

<Warning>
  Prefer [`LMOVE`](/docs/redis/commands/list/lmove) with `RIGHT` and `LEFT` in new code: `LMOVE <source> <destination> RIGHT LEFT`.
</Warning>

Use `RPOPLPUSH` to atomically move an element from the tail of one list to the head of another, returning the element.

If the source is empty nothing happens and the reply is null. Source and destination may be the same key, which rotates the list: the tail element becomes the new head, so repeated calls cycle through a list of items forever, a handy pattern for round-robin scheduling.

[`LMOVE`](/docs/redis/commands/list/lmove) does the same thing but lets you choose both ends, and [`BRPOPLPUSH`](/docs/redis/commands/list/brpoplpush) is the blocking form.

## Syntax

```redis
RPOPLPUSH <source> <destination>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<source>` | Yes | No | Redis key used as source. |
| `<destination>` | Yes | No | Redis key used as destination. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string or Null bulk string or null array |
| RESP3 | Bulk string or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
RPOPLPUSH source-key destination-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.rpoplpush("source-key", "destination-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.rpoplpush("source-key", "destination-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.rPopLPush("source-key", "destination-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.rpoplpush("source-key", "destination-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.RPopLPush(context.Background(), "source-key", "destination-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.rpoplpush("source-key", "destination-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.rpoplpush("source-key", "destination-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# RPUSH
Source: https://upstash.com/docs/redis/commands/list/rpush

Use `RPUSH` to add one or more elements to the tail of a list, creating the list when the key does not exist.

Elements are appended in the order given, so `RPUSH key a b c` leaves the list as `a`, `b`, `c`. The reply is the length of the list after the push, and a key holding another type returns an error.

Appending with `RPUSH` and consuming from the head with [`LPOP`](/docs/redis/commands/list/lpop) is the standard first-in, first-out queue, and it is the form most job queues use because the natural reading order with [`LRANGE`](/docs/redis/commands/list/lrange) then matches the order of insertion.

## Syntax

```redis
RPUSH <key> <element> [<element> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<element>` | Yes | Yes | Element to push. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
RPUSH my-key element
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const length1 = await redis.rpush("key", "a", "b", "c");
console.log(length1); // 3
const length2 = await redis.rpush("key", "d");
console.log(length2); // 4
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.rpush("my-key", "element")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.rpush("my-key", "element");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.rPush("my-key", "element");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.rpush("my-key", "element")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.RPush(context.Background(), "my-key", "element").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.rpush("my-key", "element");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.rpush("my-key", "element")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# RPUSHX
Source: https://upstash.com/docs/redis/commands/list/rpushx

Use `RPUSHX` to add elements to the tail of a list only when the list already exists.

Nothing happens and the reply is `0` when the key does not exist, and no key is created. Use it when producers should append to a queue only while it is alive, for example when the list is created by a consumer that is currently attached, so that writes to a queue nobody is reading do not silently pile up.

## Syntax

```redis
RPUSHX <key> <element> [<element> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<element>` | Yes | Yes | Element to push. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
RPUSHX my-key element
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.lpush("key", "a", "b", "c");
const length = await redis.rpushx("key", "d");
console.log(length); // 4
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.rpushx("my-key", "element")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.rpushx("my-key", "element");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.rPushX("my-key", "element");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.rpushx("my-key", "element")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.RPushX(context.Background(), "my-key", "element").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.rpushx("my-key", "element");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.rpush_exists("my-key", "element")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# Redis command reference
Source: https://upstash.com/docs/redis/commands/overview

Upstash supports Redis commands over both native Redis TCP and HTTPS REST. Choose a category to browse its supported commands.

<CardGroup cols={2}>
<Card title="Bitmap" icon="grid" href="/redis/commands/bitmap/overview" horizontal>Manipulate individual bits and bit fields</Card>
<Card title="Connection" icon="plug" href="/redis/commands/connection/overview" horizontal>Authenticate and manage client connections</Card>
<Card title="Functions" icon="code" href="/redis/commands/functions/overview" horizontal>Load, manage, and invoke Redis functions</Card>
<Card title="Generic" icon="sliders" href="/redis/commands/generic/overview" horizontal>Manage keys, expiration, and serialization</Card>
<Card title="Geo" icon="location-dot" href="/redis/commands/geo/overview" horizontal>Store and query geospatial data</Card>
<Card title="Hash" icon="hashtag" href="/redis/commands/hash/overview" horizontal>Work with field-value collections</Card>
<Card title="HyperLogLog" icon="chart-simple" href="/redis/commands/hyperloglog/overview" horizontal>Estimate cardinality</Card>
<Card title="JSON" icon="brackets-curly" href="/redis/commands/json/overview" horizontal>Store and manipulate JSON values</Card>
<Card title="List" icon="list" href="/redis/commands/list/overview" horizontal>Work with ordered collections</Card>
<Card title="Pub/Sub" icon="tower-broadcast" href="/redis/commands/pub-sub/overview" horizontal>Publish messages and manage subscriptions</Card>
<Card title="Scripting" icon="scroll" href="/redis/commands/scripting/overview" horizontal>Load and execute Lua scripts</Card>
<Card title="Search" icon="magnifying-glass" href="/redis/commands/search/overview" horizontal>Create, query, and manage search indexes</Card>
<Card title="Server" icon="server" href="/redis/commands/server/overview" horizontal>Inspect and manage the Redis server</Card>
<Card title="Set" icon="layer-group" href="/redis/commands/set/overview" horizontal>Work with unique unordered members</Card>
<Card title="Sorted Set" icon="arrow-down-1-9" href="/redis/commands/sorted-set/overview" horizontal>Work with scored collections</Card>
<Card title="Streams" icon="water" href="/redis/commands/streams/overview" horizontal>Process append-only logs and consumer groups</Card>
<Card title="String" icon="font" href="/redis/commands/string/overview" horizontal>Store and manipulate string values</Card>
<Card title="Transactions" icon="right-left" href="/redis/commands/transactions/overview" horizontal>Group operations into atomic transactions</Card>
</CardGroup>

# Pub/Sub commands
Source: https://upstash.com/docs/redis/commands/pub-sub/overview

<CardGroup cols={2}>
<Card title="PSUBSCRIBE" href="/redis/commands/pub-sub/psubscribe">Subscribe to pattern channels</Card>
<Card title="PUBLISH" href="/redis/commands/pub-sub/publish">Publish message to channel</Card>
<Card title="PUBSUB" href="/redis/commands/pub-sub/pubsub">Inspect pub/sub state</Card>
<Card title="PUNSUBSCRIBE" href="/redis/commands/pub-sub/punsubscribe">Unsubscribe from patterns</Card>
<Card title="SUBSCRIBE" href="/redis/commands/pub-sub/subscribe">Subscribe to channels</Card>
<Card title="UNSUBSCRIBE" href="/redis/commands/pub-sub/unsubscribe">Unsubscribe from channels</Card>
</CardGroup>

# PSUBSCRIBE
Source: https://upstash.com/docs/redis/commands/pub-sub/psubscribe

Use `PSUBSCRIBE` to subscribe the current connection to channels by glob-style pattern.

A pattern such as `news.*` matches every channel that starts with `news.`, including channels created after the subscription, which is what makes patterns useful for topic hierarchies. `?` matches a single character and `[...]` a character class.

Pattern subscriptions are tracked separately from the exact-channel subscriptions made with [`SUBSCRIBE`](/docs/redis/commands/pub-sub/subscribe), and a message that matches several of a connection's patterns is delivered once per matching pattern, so overlapping patterns produce duplicates. Cancel a pattern with [`PUNSUBSCRIBE`](/docs/redis/commands/pub-sub/punsubscribe), passing exactly the same pattern string.

## Syntax

```redis
PSUBSCRIBE <pattern> [<pattern> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<pattern>` | Yes | Yes | Glob-style channel pattern. |

## Important points

* This is a connection-oriented command and is available over native Redis TCP, not the stateless REST endpoint.
* Subscription commands require a dedicated TCP connection. In RESP3, subscription events use push replies.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Three-element subscription-state array per pattern |
| RESP3 | Three-element subscription-state push reply per pattern |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
PSUBSCRIBE events:*
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const subscriber = new Redis(process.env.REDIS_URL!);
await subscriber.psubscribe("events:*");
subscriber.on("message", (channel, message) => console.log(channel, message));
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const subscriber = await createClient({ url: process.env.REDIS_URL }).connect();
await subscriber.pSubscribe("events:*", (message, channel) => {
  console.log(channel, message);
});
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
pubsub = client.pubsub()
pubsub.psubscribe("events:*")
for message in pubsub.listen():
    print(message)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    ctx := context.Background()
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    pubsub := client.PSubscribe(ctx, "events:*")
    for message := range pubsub.Channel() {
        fmt.Println(message.Channel, message.Payload)
    }
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  jedis.psubscribe(new JedisPubSub() {
    @Override
    public void onPMessage(String pattern, String channel, String message) {
      System.out.println(channel + ": " + message);
    }
  }, "events:*");
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;
    let mut pubsub = connection.as_pubsub();
    pubsub.psubscribe("events:*")?;
    loop {
        let message = pubsub.get_message()?;
        let payload: String = message.get_payload()?;
        println!("{payload}");
    }
    #[allow(unreachable_code)]
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# PUBLISH
Source: https://upstash.com/docs/redis/commands/pub-sub/publish

Use `PUBLISH` to send a message to a channel.

The reply is the number of subscribers the message was delivered to, counting both channel and pattern subscribers, so a reply of `0` means nobody was listening. Delivery is fire and forget: messages are not stored and a client that is not connected at that moment never sees them, so use [streams](/docs/redis/commands/streams/overview) when messages must survive a disconnect or be replayed.

Publishing works over both the REST API and a TCP connection, while subscribing requires a TCP connection.

## Syntax

```redis
PUBLISH <channel> <message>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<channel>` | Yes | No | Channel name. |
| `<message>` | Yes | No | Message payload. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
PUBLISH events hello
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const listeners = await redis.publish("my-channel", "my-message");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.publish("events", "hello")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.publish("events", "hello");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.publish("events", "hello");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.publish("events", "hello")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Publish(context.Background(), "events", "hello").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.publish("events", "hello");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.publish("events", "hello")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# PUBSUB
Source: https://upstash.com/docs/redis/commands/pub-sub/pubsub

Use `PUBSUB` to inspect the state of the pub/sub system without subscribing to anything.

`CHANNELS` lists the channels that currently have at least one subscriber, optionally filtered by a glob-style pattern. `NUMSUB` reports the subscriber count for each channel you name, and `NUMPAT` reports how many distinct patterns are subscribed to across all clients.

Only exact-channel subscriptions are counted by `CHANNELS` and `NUMSUB`: a client subscribed with [`PSUBSCRIBE`](/docs/redis/commands/pub-sub/psubscribe) will receive matching messages but does not make a channel appear as active. Use these forms for monitoring and debugging, for instance to confirm that a consumer is really attached before publishing.

## Syntax

```redis
PUBSUB CHANNELS [pattern]
PUBSUB NUMSUB [channel ...]
PUBSUB NUMPAT
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `CHANNELS [pattern]` | One form | No | List active channels, optionally filtered by a glob-style pattern. |
| `NUMSUB [channel ...]` | One form | Yes | Return subscriber counts for the supplied channels. |
| `NUMPAT` | One form | No | Return the number of active pattern subscriptions. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of channel names, flat channel/count array, or Integer |
| RESP3 | Array of channel names, flat channel/count array, or Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
PUBSUB CHANNELS events:*
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.pubsub("CHANNELS", "events:*");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.pubSubChannels("events:*");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.pubsub_channels("events:*")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.PubSubChannels(context.Background(), "events:*").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.pubsubChannels("events:*");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("PUBSUB");
    command.arg("CHANNELS");
    command.arg("events:*");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# PUNSUBSCRIBE
Source: https://upstash.com/docs/redis/commands/pub-sub/punsubscribe

Use `PUNSUBSCRIBE` to cancel pattern subscriptions of the current connection.

With no arguments the connection unsubscribes from every pattern it registered, otherwise only from the patterns named, which must be given exactly as they were passed to [`PSUBSCRIBE`](/docs/redis/commands/pub-sub/psubscribe), since patterns are matched literally here and not expanded. The server sends one confirmation per pattern with the number of subscriptions still active.

Exact-channel subscriptions are not affected; cancel those with [`UNSUBSCRIBE`](/docs/redis/commands/pub-sub/unsubscribe).

## Syntax

```redis
PUNSUBSCRIBE [<pattern> [<pattern> ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<pattern>` | No | Yes | Glob-style channel pattern; omit to unsubscribe from all patterns. |

## Important points

* This is a connection-oriented command and is available over native Redis TCP, not the stateless REST endpoint.
* Subscription commands require a dedicated TCP connection. In RESP3, subscription events use push replies.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Three-element subscription-state array per pattern |
| RESP3 | Three-element subscription-state push reply per pattern |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
PUNSUBSCRIBE events:*
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const subscriber = new Redis(process.env.REDIS_URL!);
await subscriber.psubscribe("events:*");
await subscriber.punsubscribe("events:*");
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const subscriber = await createClient({ url: process.env.REDIS_URL }).connect();
await subscriber.pSubscribe("events:*", console.log);
await subscriber.pUnsubscribe("events:*");
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
pubsub = client.pubsub()
pubsub.psubscribe("events:*")
pubsub.punsubscribe("events:*")
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    ctx := context.Background()
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    pubsub := client.PSubscribe(ctx, "events:*")
    if err := pubsub.PUnsubscribe(ctx, "events:*"); err != nil {
        panic(err)
    }
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  jedis.psubscribe(new JedisPubSub() {
    @Override
    public void onPMessage(String pattern, String channel, String message) {
      punsubscribe();
    }
  }, "events:*");
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;
    let mut pubsub = connection.as_pubsub();
    pubsub.psubscribe("events:*")?;
    pubsub.punsubscribe("events:*")?;
    #[allow(unreachable_code)]
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SUBSCRIBE
Source: https://upstash.com/docs/redis/commands/pub-sub/subscribe

Use `SUBSCRIBE` to subscribe the current connection to one or more channels.

The server confirms each channel with its own reply carrying the running number of subscriptions this connection holds, and from then on messages published to those channels arrive on the connection as they are sent.

Under RESP2 a subscribed connection may only run subscription commands plus `PING`, `RESET`, and `QUIT`, which is why subscribers normally use a dedicated connection. Under RESP3 messages arrive as push replies and ordinary commands remain usable on the same connection. Messages published while the connection is not subscribed are not delivered later, since pub/sub keeps no history.

## Syntax

```redis
SUBSCRIBE <channel> [<channel> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<channel>` | Yes | Yes | Channel name. |

## Important points

* This is a connection-oriented command and is available over native Redis TCP, not the stateless REST endpoint.
* Subscription commands require a dedicated TCP connection. In RESP3, subscription events use push replies.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Three-element subscription-state array per channel |
| RESP3 | Three-element subscription-state push reply per channel |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SUBSCRIBE events
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const subscriber = new Redis(process.env.REDIS_URL!);
await subscriber.subscribe("events");
subscriber.on("message", (channel, message) => console.log(channel, message));
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const subscriber = await createClient({ url: process.env.REDIS_URL }).connect();
await subscriber.subscribe("events", (message, channel) => {
  console.log(channel, message);
});
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
pubsub = client.pubsub()
pubsub.subscribe("events")
for message in pubsub.listen():
    print(message)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    ctx := context.Background()
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    pubsub := client.Subscribe(ctx, "events")
    for message := range pubsub.Channel() {
        fmt.Println(message.Channel, message.Payload)
    }
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  jedis.subscribe(new JedisPubSub() {
    @Override
    public void onMessage(String channel, String message) {
      System.out.println(channel + ": " + message);
    }
  }, "events");
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;
    let mut pubsub = connection.as_pubsub();
    pubsub.subscribe("events")?;
    loop {
        let message = pubsub.get_message()?;
        let payload: String = message.get_payload()?;
        println!("{payload}");
    }
    #[allow(unreachable_code)]
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# UNSUBSCRIBE
Source: https://upstash.com/docs/redis/commands/pub-sub/unsubscribe

Use `UNSUBSCRIBE` to cancel channel subscriptions of the current connection.

With no arguments the connection unsubscribes from every channel it is subscribed to, otherwise only from the ones named. The server sends one confirmation per channel, each carrying the number of subscriptions still active, and the connection leaves subscriber mode once that count reaches zero.

Pattern subscriptions are not affected; cancel those with [`PUNSUBSCRIBE`](/docs/redis/commands/pub-sub/punsubscribe).

## Syntax

```redis
UNSUBSCRIBE [<channel> [<channel> ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<channel>` | No | Yes | Channel name. |

## Important points

* This is a connection-oriented command and is available over native Redis TCP, not the stateless REST endpoint.
* Subscription commands require a dedicated TCP connection. In RESP3, subscription events use push replies.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Three-element subscription-state array per channel |
| RESP3 | Three-element subscription-state push reply per channel |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
UNSUBSCRIBE events
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const subscriber = new Redis(process.env.REDIS_URL!);
await subscriber.subscribe("events");
await subscriber.unsubscribe("events");
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const subscriber = await createClient({ url: process.env.REDIS_URL }).connect();
await subscriber.subscribe("events", console.log);
await subscriber.unsubscribe("events");
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
pubsub = client.pubsub()
pubsub.subscribe("events")
pubsub.unsubscribe("events")
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    ctx := context.Background()
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    pubsub := client.Subscribe(ctx, "events")
    if err := pubsub.Unsubscribe(ctx, "events"); err != nil {
        panic(err)
    }
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  jedis.subscribe(new JedisPubSub() {
    @Override
    public void onMessage(String channel, String message) {
      unsubscribe();
    }
  }, "events");
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;
    let mut pubsub = connection.as_pubsub();
    pubsub.subscribe("events")?;
    pubsub.unsubscribe("events")?;
    #[allow(unreachable_code)]
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# EVAL
Source: https://upstash.com/docs/redis/commands/scripting/eval

Use `EVAL` to run a Lua script on the server.

`<numkeys>` says how many of the arguments that follow are key names. The script receives those in the `KEYS` table and every remaining argument in `ARGV`. Passing key names as keys rather than hardcoding them in the script body matters, because Redis uses that list for routing and access checks. Inside the script, `redis.call` runs Redis commands and its return value is converted to a Lua value.

The script runs as a single atomic step, which makes it the standard way to do read, decide, and write logic, such as a rate limiter or a compare-and-set update, in one round trip and without a transaction. Keep scripts short, since a script that holds the database blocks everything else, and keep them deterministic by deriving values from `KEYS`, `ARGV`, or data read inside the script rather than from clock or random sources.

Upstash isolates a script with a lock. By default that is the global lock, because the engine cannot know in advance which keys the script will touch, so no other command runs while the script does. Adding the `allow-key-locking` flag to the script's shebang line makes it lock only the keys passed in `KEYS` instead, so calls that work on disjoint keys run in parallel:

```lua
#!lua flags=allow-key-locking

redis.call('INCR', KEYS[1])
return 1
```

With the flag set, every key the script touches must appear in `KEYS`, and commands that need database-wide access, such as `FLUSHDB`, are rejected. See [Key-Based Locking](/docs/redis/features/key-locking) for the full rules.

<Warning>
  Pass every key the script touches through `KEYS`, even when the script runs
  under the global lock. Upstash keeps idle entries
  [on disk](/docs/redis/features/durability): declared keys are loaded before the
  script starts and the lock is released during that read, but a key that the
  script builds while it runs is read from disk with the lock held, stalling
  every command waiting on it. See
  [Dynamic Keys and Latency](/docs/redis/features/key-locking#dynamic-keys-and-latency).
</Warning>

Sending a script also caches it under its SHA1 digest, so later calls can use [`EVALSHA`](/docs/redis/commands/scripting/evalsha) and avoid resending the body. Use [`EVAL_RO`](/docs/redis/commands/scripting/eval-ro) for scripts that only read.

## Syntax

```redis
EVAL <script> <numkeys> [<key> [<key> ...]] [<arg> [<arg> ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<script>` | Yes | No | Lua script source. |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | No | Yes | Redis key targeted by the command. |
| `<arg>` | No | Yes | Additional argument, available to the script as `ARGV`. |

## Important points

* `numkeys` must equal the number of key arguments that immediately follow it; remaining arguments are available to the script or function as ordinary arguments.
* The script takes the global lock unless its shebang sets the `allow-key-locking` flag, in which case it locks only the keys passed in `KEYS`. See [Key-Based Locking](/docs/redis/features/key-locking).
* A script queued inside a `MULTI`/`EXEC` transaction always runs under the global lock, even when it sets `allow-key-locking`. Call it directly if you want per-key locking.
* Pass every key the script touches through `KEYS` whether or not `allow-key-locking` is set. A key built inside the script is read from disk under the lock when it is not in memory, and it is rejected outright when the flag is set. See [Dynamic Keys and Latency](/docs/redis/features/key-locking#dynamic-keys-and-latency).

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Reply produced by the evaluated script |
| RESP3 | Reply produced by the evaluated script |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
EVAL "return ARGV[1]" 0 hello
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const script = `
    return ARGV[1]
`
const result = await redis.eval(script, [], ["hello"]);
console.log(result) // "hello"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.eval("return ARGV[1]", args=["value"])
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.eval("return ARGV[1]", "0", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.eval("return ARGV[1]", { arguments: ["value"] });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.eval("return ARGV[1]", 0, "value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Eval(context.Background(), "return ARGV[1]", nil, "value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.eval("return ARGV[1]", java.util.List.of(), java.util.List.of("value"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("EVAL");
    command.arg("return ARGV[1]");
    command.arg("1");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# EVAL_RO
Source: https://upstash.com/docs/redis/commands/scripting/eval-ro

Use `EVAL_RO` to run a Lua script that is not allowed to write.

It behaves like [`EVAL`](/docs/redis/commands/scripting/eval), with `<numkeys>` splitting the arguments into the `KEYS` and `ARGV` tables, except that any write command called from the script fails with an error. Declaring the read-only intent lets the server serve the call on replicas and turns an accidental write into a clear error instead of an unexpected modification, which is worth doing for every script that only computes over existing data.

Being read-only is not by itself enough to run concurrently with other commands: like `EVAL`, this command takes the global lock unless the script's shebang sets the `allow-key-locking` flag. With the flag, the script takes shared read locks on the keys passed in `KEYS`, so several readers of the same key proceed together. Combine the two flags with a comma, as in `#!lua flags=no-writes,allow-key-locking`, and see [Key-Based Locking](/docs/redis/features/key-locking) for the full rules.

## Syntax

```redis
EVAL_RO <script> <numkeys> [<key> [<key> ...]] [<arg> [<arg> ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<script>` | Yes | No | Lua script source. |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | No | Yes | Redis key targeted by the command. |
| `<arg>` | No | Yes | Additional argument, available to the script as `ARGV`. |

## Important points

* `numkeys` must equal the number of key arguments that immediately follow it; remaining arguments are available to the script or function as ordinary arguments.
* A read-only script still takes the global lock unless its shebang sets the `allow-key-locking` flag. See [Key-Based Locking](/docs/redis/features/key-locking).
* Pass every key the script reads through `KEYS` whether or not `allow-key-locking` is set. A key built inside the script is read from disk under the lock when it is not in memory, and it is rejected outright when the flag is set. See [Dynamic Keys and Latency](/docs/redis/features/key-locking#dynamic-keys-and-latency).

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Reply produced by the evaluated read-only script |
| RESP3 | Reply produced by the evaluated read-only script |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
EVAL_RO "return ARGV[1]" 0 hello
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const script = `
    return ARGV[1]
`
const result = await redis.evalRo(script, [], ["hello"]);
console.log(result) // "hello"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.eval_ro("return ARGV[1]", args=["value"])
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.eval_ro("return ARGV[1]", "0", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.evalRo("return ARGV[1]", { arguments: ["value"] });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.eval_ro("return ARGV[1]", 0, "value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.EvalRO(context.Background(), "return ARGV[1]", nil, "value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.evalReadonly("return ARGV[1]", java.util.List.of(), java.util.List.of("value"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("EVAL_RO");
    command.arg("return ARGV[1]");
    command.arg("1");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# EVALSHA
Source: https://upstash.com/docs/redis/commands/scripting/evalsha

Use `EVALSHA` to run a script that is already in the server's script cache, identified by its SHA1 digest.

It behaves exactly like [`EVAL`](/docs/redis/commands/scripting/eval) but sends only the 40-character digest instead of the script body, which keeps the request small when a script is called often. The digest is what [`SCRIPT LOAD`](/docs/redis/commands/scripting/script-load) returns, and it is also computed as a side effect of any `EVAL` call.

Locking behaviour comes from the cached script body, not from the call: the script takes the global lock unless its shebang sets the `allow-key-locking` flag, in which case only the keys passed in `KEYS` are locked. Because the flag lives in the body, changing it means loading a new script and calling the new digest. See [Key-Based Locking](/docs/redis/features/key-locking).

When the script is not in the cache the server replies with a `NOSCRIPT` error, and the client is expected to fall back to `EVAL`. Most client libraries do this automatically. The cache does not survive a restart and is cleared by [`SCRIPT FLUSH`](/docs/redis/commands/scripting/script-flush), so applications must always be able to resend the script body.

## Syntax

```redis
EVALSHA <sha1> <numkeys> [<key> [<key> ...]] [<arg> [<arg> ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<sha1>` | Yes | No | SHA1 digest of a script cached with `SCRIPT LOAD`. |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | No | Yes | Redis key targeted by the command. |
| `<arg>` | No | Yes | Additional argument, available to the script as `ARGV`. |

## Important points

* `numkeys` must equal the number of key arguments that immediately follow it; remaining arguments are available to the script or function as ordinary arguments.
* The cached script takes the global lock unless its shebang sets the `allow-key-locking` flag. See [Key-Based Locking](/docs/redis/features/key-locking).
* A script queued inside a `MULTI`/`EXEC` transaction always runs under the global lock, even when it sets `allow-key-locking`. Call it directly if you want per-key locking.
* Pass every key the script touches through `KEYS` whether or not `allow-key-locking` is set. A key built inside the script is read from disk under the lock when it is not in memory, and it is rejected outright when the flag is set. See [Dynamic Keys and Latency](/docs/redis/features/key-locking#dynamic-keys-and-latency).

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Reply produced by the cached script |
| RESP3 | Reply produced by the cached script |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
EVALSHA fb67a0c03b48ddbf8b4c9b011e779563bdbc28cb 0 value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const result = await redis.evalsha("fb67a0c03b48ddbf8b4c9b011e779563bdbc28cb", [], ["hello"]);
console.log(result) // "hello"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.evalsha("<sha1>", args=["value"])
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.evalsha("<sha1>", "0", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.evalSha("<sha1>", { arguments: ["value"] });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.evalsha("<sha1>", 0, "value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.EvalSha(context.Background(), "<sha1>", nil, "value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.evalsha("<sha1>", java.util.List.of(), java.util.List.of("value"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("EVALSHA");
    command.arg("<sha1>");
    command.arg("1");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# EVALSHA_RO
Source: https://upstash.com/docs/redis/commands/scripting/evalsha-ro

Use `EVALSHA_RO` to run a script cached by its SHA1 digest. The script may not write to the database.

It is the read-only form of [`EVALSHA`](/docs/redis/commands/scripting/evalsha): any write command called from the script fails, which lets the server run it on replicas. As with `EVALSHA`, a digest that is not in the cache produces a `NOSCRIPT` error, and the caller is expected to fall back to [`EVAL_RO`](/docs/redis/commands/scripting/eval-ro) with the script body.

Read-only scripts take the global lock like any other script unless the cached body's shebang sets the `allow-key-locking` flag, for example `#!lua flags=no-writes,allow-key-locking`. With the flag, the call takes shared read locks on the keys passed in `KEYS`. See [Key-Based Locking](/docs/redis/features/key-locking).

## Syntax

```redis
EVALSHA_RO <sha1> <numkeys> [<key> [<key> ...]] [<arg> [<arg> ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<sha1>` | Yes | No | SHA1 digest of a script cached with `SCRIPT LOAD`. |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | No | Yes | Redis key targeted by the command. |
| `<arg>` | No | Yes | Additional argument, available to the script as `ARGV`. |

## Important points

* `numkeys` must equal the number of key arguments that immediately follow it; remaining arguments are available to the script or function as ordinary arguments.
* A read-only script still takes the global lock unless the cached body's shebang sets the `allow-key-locking` flag. See [Key-Based Locking](/docs/redis/features/key-locking).
* Pass every key the script reads through `KEYS` whether or not `allow-key-locking` is set. A key built inside the script is read from disk under the lock when it is not in memory, and it is rejected outright when the flag is set. See [Dynamic Keys and Latency](/docs/redis/features/key-locking#dynamic-keys-and-latency).

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Reply produced by the cached read-only script |
| RESP3 | Reply produced by the cached read-only script |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
EVALSHA_RO fb67a0c03b48ddbf8b4c9b011e779563bdbc28cb 0 value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const result = await redis.evalshaRo("fb67a0c03b48ddbf8b4c9b011e779563bdbc28cb", [], ["hello"]);
console.log(result) // "hello"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.evalsha_ro("<sha1>", args=["value"])
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.evalsha_ro("<sha1>", "0", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.evalShaRo("<sha1>", { arguments: ["value"] });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.evalsha_ro("<sha1>", 0, "value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.EvalShaRO(context.Background(), "<sha1>", nil, "value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.evalshaReadonly("<sha1>", java.util.List.of(), java.util.List.of("value"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("EVALSHA_RO");
    command.arg("<sha1>");
    command.arg("1");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# Scripting commands
Source: https://upstash.com/docs/redis/commands/scripting/overview

<CardGroup cols={2}>
<Card title="EVAL" href="/redis/commands/scripting/eval">Execute a Lua script</Card>
<Card title="EVAL_RO" href="/redis/commands/scripting/eval-ro">Execute read-only Lua script</Card>
<Card title="EVALSHA" href="/redis/commands/scripting/evalsha">Execute cached Lua script by SHA</Card>
<Card title="EVALSHA_RO" href="/redis/commands/scripting/evalsha-ro">Read-only execute by SHA</Card>
<Card title="SCRIPT EXISTS" href="/redis/commands/scripting/script-exists">Check if scripts exist in cache</Card>
<Card title="SCRIPT FLUSH" href="/redis/commands/scripting/script-flush">Clear the script cache</Card>
<Card title="SCRIPT KILL" href="/redis/commands/scripting/script-kill">Stop a running read-only script</Card>
<Card title="SCRIPT LOAD" href="/redis/commands/scripting/script-load">Load script into cache</Card>
</CardGroup>

# SCRIPT EXISTS
Source: https://upstash.com/docs/redis/commands/scripting/script-exists

Use `SCRIPT EXISTS` to check which of the given SHA1 digests are present in the script cache.

The reply holds one `1` or `0` per digest, in the order they were given. Client libraries use it to find out, in a single call, which of their scripts still need to be loaded before they can be invoked with [`EVALSHA`](/docs/redis/commands/scripting/evalsha), instead of discovering the gap through a `NOSCRIPT` error at call time.

## Syntax

```redis
SCRIPT EXISTS <sha1> [<sha1> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<sha1>` | Yes | Yes | SHA1 digest of a script cached with `SCRIPT LOAD`. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integers, one per SHA1 digest |
| RESP3 | Array of integers, one per SHA1 digest |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SCRIPT EXISTS fb67a0c03b48ddbf8b4c9b011e779563bdbc28cb
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.scriptExists("<sha1>", "<sha2>")

// Returns 1
// [1, 0]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.script_exists("<sha1>")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.script("EXISTS", "<sha1>");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.scriptExists("<sha1>");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.script_exists("<sha1>")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ScriptExists(context.Background(), "<sha1>").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.scriptExists("<sha1>");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("SCRIPT");
    command.arg("EXISTS");
    command.arg("<sha1>");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SCRIPT FLUSH
Source: https://upstash.com/docs/redis/commands/scripting/script-flush

Use `SCRIPT FLUSH` to remove every script from the server's script cache.

After the flush, [`EVALSHA`](/docs/redis/commands/scripting/evalsha) calls fail with `NOSCRIPT` until the scripts are loaded again, so run it when you want clients to resend their current script bodies, typically after deploying changed scripts. `ASYNC` reclaims the memory in the background and `SYNC` reclaims it before the reply. Cached scripts are not part of the dataset, so flushing them does not touch your data.

## Syntax

```redis
SCRIPT FLUSH [ASYNC | SYNC]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `(ASYNC \| SYNC)` | No | No | Choose one form: `ASYNC` (request asynchronous cleanup); `SYNC` (request synchronous cleanup). |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SCRIPT FLUSH
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.scriptFlush();
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.script_flush()
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.script("FLUSH");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.scriptFlush();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.script_flush()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ScriptFlush(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.scriptFlush();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("SCRIPT");
    command.arg("FLUSH");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SCRIPT KILL
Source: https://upstash.com/docs/redis/commands/scripting/script-kill

Use `SCRIPT KILL` to stop a running read-only script.

Only a script that has not yet written anything can be killed: stopping one halfway through its writes would leave the dataset in a state that no atomic execution could produce, so Redis refuses and the server has to be restarted instead. This is the main reason to keep scripts short and to use [`EVAL_RO`](/docs/redis/commands/scripting/eval-ro) wherever a script only reads.

The current Upstash deployment recognizes the command but has no interruptible running-script state to act on, so it replies with a `NOTBUSY` error.

## Syntax

```redis
SCRIPT KILL
```

## Arguments

This command takes no arguments.

## Important points

* The current deployment recognizes this command but reports `NOTBUSY` because it does not expose an interruptible running-script state.
* As with Redis, a script that has already performed writes must not be killed because doing so would violate atomicity.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Error reply (`NOTBUSY` on the current deployment) |
| RESP3 | Error reply (`NOTBUSY` on the current deployment) |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SCRIPT KILL
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.script("KILL");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.scriptKill();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.script_kill()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ScriptKill(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.scriptKill();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("SCRIPT");
    command.arg("KILL");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SCRIPT LOAD
Source: https://upstash.com/docs/redis/commands/scripting/script-load

Use `SCRIPT LOAD` to compile a script and put it in the server's script cache without running it.

The reply is the script's SHA1 digest, which is what [`EVALSHA`](/docs/redis/commands/scripting/evalsha) takes. Loading the same script twice is harmless and always yields the same digest, so applications typically load their scripts once at startup and then call them by digest.

The cache is lost on restart and cleared by [`SCRIPT FLUSH`](/docs/redis/commands/scripting/script-flush), so keep the script source available and be ready to handle a `NOSCRIPT` error by loading it again.

Shebang flags are part of the source, so they are fixed at load time and a change to them produces a different digest. This includes `allow-key-locking`, which opts the script out of the global lock and into locking only the keys passed in `KEYS`; see [Key-Based Locking](/docs/redis/features/key-locking).

Whether or not you set that flag, write the script so that every key it touches arrives through `KEYS` rather than being assembled from `ARGV` inside the script, since an undeclared key can force a disk read while the lock is held. See [Dynamic Keys and Latency](/docs/redis/features/key-locking#dynamic-keys-and-latency).

## Syntax

```redis
SCRIPT LOAD <script>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<script>` | Yes | No | Lua script source. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string |
| RESP3 | Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SCRIPT LOAD "return ARGV[1]"
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const script = `
  local value = redis.call('GET', KEYS[1])
  return value
`;
const sha1 = await redis.scriptLoad(script);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.script_load("return ARGV[1]")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.script("LOAD", "return ARGV[1]");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.scriptLoad("return ARGV[1]");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.script_load("return ARGV[1]")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ScriptLoad(context.Background(), "return ARGV[1]").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.scriptLoad("return ARGV[1]");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::{Script, TypedCommands};

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let script = Script::new("return ARGV[1]");
    let result: String = connection.load_script(&script)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# Search commands
Source: https://upstash.com/docs/redis/commands/search/overview

<Note>
  Upstash Redis Search uses `SEARCH.*` commands. They are separate from and incompatible with the `FT.*` commands in the open-source RediSearch module.
</Note>

For Search commands, use the Upstash [`@upstash/redis`](/docs/redis/sdks/ts/overview) or [`upstash-redis`](/docs/redis/sdks/py/overview) SDK,
or the redis-js wrappers [`@upstash/search-redis`](/docs/redis/search/adapters/node-redis) for node-redis and [`@upstash/search-ioredis`](/docs/redis/search/adapters/ioredis) for ioredis.

For feature guides and tutorials, start with the [Upstash Redis Search introduction](/docs/redis/search/introduction).

<CardGroup cols={2}>
<Card title="SEARCH.CREATE" href="/redis/commands/search/search-create">Create a search index</Card>
<Card title="SEARCH.REINDEX" href="/redis/commands/search/search-reindex">Rebuild a search index from the current data</Card>
<Card title="SEARCH.DROP" href="/redis/commands/search/search-drop">Remove a search index</Card>
<Card title="SEARCH.DESCRIBE" href="/redis/commands/search/search-describe">Return metadata about a search index</Card>
<Card title="SEARCH.WAITINDEXING" href="/redis/commands/search/search-waitindexing">Wait until pending index updates are visible</Card>
<Card title="SEARCH.LISTINDEXES" href="/redis/commands/search/search-listindexes">List search indexes in the database</Card>
<Card title="SEARCH.QUERY" href="/redis/commands/search/search-query">Search documents with a JSON filter</Card>
<Card title="SEARCH.COUNT" href="/redis/commands/search/search-count">Count matching documents</Card>
<Card title="SEARCH.AGGREGATE" href="/redis/commands/search/search-aggregate">Compute analytics over matching documents</Card>
<Card title="SEARCH.ALIASADD" href="/redis/commands/search/search-aliasadd">Add or update an index alias</Card>
<Card title="SEARCH.ALIASDEL" href="/redis/commands/search/search-aliasdel">Remove an index alias</Card>
<Card title="SEARCH.LISTALIASES" href="/redis/commands/search/search-listaliases">List index aliases</Card>
</CardGroup>

`SEARCH.QUERY`, `SEARCH.COUNT`, `SEARCH.AGGREGATE`, `SEARCH.DESCRIBE`, `SEARCH.WAITINDEXING`, and `SEARCH.REINDEX` accept either an index name or alias. `SEARCH.DROP` always treats its argument as a literal index name. This makes it possible to switch an alias to a replacement index and then drop the old index without dropping the replacement.

## REST API Usage

All search commands can be sent via the Upstash REST API using a JSON array POST body.
Each element of the array corresponds to a token in the command.

```bash
curl -X POST https://YOUR_ENDPOINT.upstash.io \
  -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
  -d '["COMMAND", "arg1", "arg2", ...]'
```

The JSON filter and aggregation arguments are passed as string values within the array (not as nested JSON objects):

```bash
# Correct — filter is a string inside the array
["SEARCH.QUERY", "products", "{\"name\": \"wireless\"}", "LIMIT", "10"]

# Incorrect — filter is a nested object
["SEARCH.QUERY", "products", {"name": "wireless"}, "LIMIT", "10"]
```

Pass JSON filter and aggregation arguments as string values inside the array, not as nested JSON objects.
Search commands also work through the `/pipeline` endpoint for batching multiple commands in a single HTTP request.

# SEARCH.AGGREGATE
Source: https://upstash.com/docs/redis/commands/search/search-aggregate

Use `SEARCH.AGGREGATE` to compute metrics and buckets over matching documents.

The command takes two JSON arguments. The first is a filter, in the same language as [`SEARCH.QUERY`](/docs/redis/commands/search/search-query), which selects the documents to aggregate; pass `'{}'` to cover the whole index. The second describes the aggregations to compute, as named entries such as `{"avg_price": {"$avg": {"field": "price"}}}`.

Metric operators like `$avg`, `$sum`, `$min`, `$max`, `$stats`, and `$cardinality` reduce the selected documents to a single number, while bucket operators like `$terms`, `$range`, `$histogram`, and `$dateHistogram` group them and report a count per bucket. Bucket operators accept nested `$aggs`, so you can compute a metric inside each bucket, for example the average price per category, and several aggregations can be requested in one call since they all run over the same selected document set.

See [Aggregations](/docs/redis/search/aggregations) for the full operator reference and examples.

## Syntax

```redis
SEARCH.AGGREGATE <name> '<json_filter>' '<json_aggregations>'
```

`<json_filter>` is an [Upstash JSON filter](/docs/redis/search/querying). The command accepts an index name or alias. See [Aggregations](/docs/redis/search/aggregations) for the aggregation object and supported operators.

## Response

In `redis-cli --json`, the response is an object keyed by aggregation alias:

```json
{
  "avg_price": { "value": 49.99 },
  "by_category": { "buckets": [{ "key": "electronics", "docCount": 42 }] }
}
```

Raw RESP output may be rendered differently by client or protocol settings, but each top-level alias maps to its aggregation result. Returns `null` if the index does not exist.

## Examples

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SEARCH.AGGREGATE products '{}' '{"avg_price": {"$avg": {"field": "price"}}}'
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const products = redis.search.index({ name: "products" });

const result = await products.aggregate({
  aggregations: {
    avg_price: { $avg: { field: "price" } },
  },
});
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
products = redis.search.index(name="products")

result = products.aggregate(
    aggregations={"avg_price": {"$avg": {"field": "price"}}},
)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import IORedis from "ioredis";
import { createSearch } from "@upstash/search-ioredis";

const redis = new IORedis(process.env.REDIS_URL!);
const search = createSearch(redis);
const products = search.index({ name: "products" });

const result = await products.aggregate({
  aggregations: {
    avg_price: { $avg: { field: "price" } },
  },
});
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";
import { createSearch } from "@upstash/search-redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const search = createSearch(client);
const products = search.index({ name: "products" });

const result = await products.aggregate({
  aggregations: {
    avg_price: { $avg: { field: "price" } },
  },
});
```

</Accordion>

<Accordion title="curl">

```bash
curl -X POST https://YOUR_ENDPOINT.upstash.io \
  -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
  -d '["SEARCH.AGGREGATE", "products", "{}", "{\"avg_price\": {\"$avg\": {\"field\": \"price\"}}}"]'
```

</Accordion>

</AccordionGroup>

# SEARCH.ALIASADD
Source: https://upstash.com/docs/redis/commands/search/search-aliasadd

Use `SEARCH.ALIASADD` to create an alias or update an existing alias to point to a search index.

An alias is a stable name that queries can use in place of a real index name, so the index behind it can be replaced without touching application code. Pointing an existing alias at a different index is atomic, which is what makes zero-downtime reindexing possible: build the new index under a fresh name, then move the alias to it in one step.

See [Aliases](/docs/redis/search/aliases#adding-an-alias) for feature examples, including zero-downtime index swaps.

The target name is not required to exist when the alias is created, so aliases can temporarily be dangling. Most commands resolve aliases, but [`SEARCH.DROP`](/docs/redis/commands/search/search-drop) always uses a literal index name.

## Syntax

```redis
SEARCH.ALIASADD <alias> <index_name>
```

## Response

Returns `1` if a new alias was created or `2` if an existing alias was updated, including when it already points to the supplied index.

## Examples

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SEARCH.ALIASADD products products-v2
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.search.alias.add({
  alias: "products",
  indexName: "products-v2",
});
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.search.alias.add(
    alias="products",
    index_name="products-v2",
)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import IORedis from "ioredis";
import { createSearch } from "@upstash/search-ioredis";

const redis = new IORedis(process.env.REDIS_URL!);
const search = createSearch(redis);
const result = await search.alias.add({
  alias: "products",
  indexName: "products-v2",
});
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";
import { createSearch } from "@upstash/search-redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const search = createSearch(client);
const result = await search.alias.add({
  alias: "products",
  indexName: "products-v2",
});
```

</Accordion>

<Accordion title="curl">

```bash
curl -X POST https://YOUR_ENDPOINT.upstash.io \
  -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
  -d '["SEARCH.ALIASADD", "products", "products-v2"]'
```

</Accordion>

</AccordionGroup>

# SEARCH.ALIASDEL
Source: https://upstash.com/docs/redis/commands/search/search-aliasdel

Use `SEARCH.ALIASDEL` to remove a search index alias.

Only the alias is removed: the index it pointed to, and the documents indexed in it, are untouched and remain reachable under their real name. Queries that still use the alias fail once it is gone.

See [Deleting an Alias](/docs/redis/search/aliases#deleting-an-alias) for the feature guide.

## Syntax

```redis
SEARCH.ALIASDEL <alias>
```

## Response

Returns `1` if the alias was deleted or `0` if the alias was not found.

## Examples

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SEARCH.ALIASDEL products
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.search.alias.delete({ alias: "products" });
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.search.alias.delete(alias="products")
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import IORedis from "ioredis";
import { createSearch } from "@upstash/search-ioredis";

const redis = new IORedis(process.env.REDIS_URL!);
const search = createSearch(redis);
const result = await search.alias.delete({ alias: "products" });
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";
import { createSearch } from "@upstash/search-redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const search = createSearch(client);
const result = await search.alias.delete({ alias: "products" });
```

</Accordion>

<Accordion title="curl">

```bash
curl -X POST https://YOUR_ENDPOINT.upstash.io \
  -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
  -d '["SEARCH.ALIASDEL", "products"]'
```

</Accordion>

</AccordionGroup>

# SEARCH.COUNT
Source: https://upstash.com/docs/redis/commands/search/search-count

Use `SEARCH.COUNT` to count documents matching a query without retrieving them.

The filter is the same JSON object that [`SEARCH.QUERY`](/docs/redis/commands/search/search-query) takes, but only the number of matching documents is computed and returned, so nothing depends on the size of the result and no `LIMIT` gets in the way of the total. That makes it the right command for result counters, pagination totals, and cheap existence checks over a filter.

See [Counting](/docs/redis/search/counting) for feature examples and common use cases.

## Syntax

```redis
SEARCH.COUNT <name> '<json_filter>'
```

`<json_filter>` is an [Upstash JSON filter](/docs/redis/search/querying). The command accepts an index name or alias.

## Response

Returns the number of matching documents as an integer. Returns `-1` if the index does not exist.

## Examples

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SEARCH.COUNT products '{"inStock": true}'
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const products = redis.search.index({ name: "products" });

const result = await products.count({
  filter: { inStock: true },
});
console.log(result.count);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
products = redis.search.index(name="products")

result = products.count(filter={"inStock": True})
print(result.count)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import IORedis from "ioredis";
import { createSearch } from "@upstash/search-ioredis";

const redis = new IORedis(process.env.REDIS_URL!);
const search = createSearch(redis);
const products = search.index({ name: "products" });

const result = await products.count({
  filter: { inStock: true },
});
console.log(result.count);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";
import { createSearch } from "@upstash/search-redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const search = createSearch(client);
const products = search.index({ name: "products" });

const result = await products.count({
  filter: { inStock: true },
});
console.log(result.count);
```

</Accordion>

<Accordion title="curl">

```bash
curl -X POST https://YOUR_ENDPOINT.upstash.io \
  -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
  -d '["SEARCH.COUNT", "products", "{\"inStock\": true}"]'
```

</Accordion>

</AccordionGroup>

# SEARCH.CREATE
Source: https://upstash.com/docs/redis/commands/search/search-create

Use `SEARCH.CREATE` to create a search index over JSON, hash, or string values.

`ON` names the type of key to index and `PREFIX` the key prefixes to watch, so the index covers exactly the keys that match both, including keys written after it was created. The `SCHEMA` then declares which fields are searchable and how: `TEXT` fields are analyzed for full-text search, with `NOSTEM` and `NOTOKENIZE` to turn parts of that off, numeric, boolean, and date fields are matched exactly and can be marked `FAST` to make them usable for sorting and scoring, and `KEYWORD` and `FACET` fields are kept whole for exact matching and faceting. `FROM` maps a schema field to a differently named field in the document.

Creating an index starts an initial scan of the matching keys, which `SKIPINITIALSCAN` skips when you only want to index data written from now on; [`SEARCH.REINDEX`](/docs/redis/commands/search/search-reindex) can run that scan later. `EXISTSOK` makes the command succeed instead of failing when the index already exists.

See [Index Management](/docs/redis/search/index-management#creating-an-index) for a feature-level guide to creating indexes and [Schema Definition](/docs/redis/search/schema-definition) for field types and schema design.

<Note>
  Upstash Redis Search uses `SEARCH.*` commands. They are separate from and incompatible with the `FT.*` commands in the open-source RediSearch module.
</Note>

## Syntax

```redis
SEARCH.CREATE <name> ON <JSON|HASH|STRING>
  PREFIX <count> <prefix> [<prefix> ...]
  [LANGUAGE <language>]
  [SKIPINITIALSCAN]
  [EXISTSOK]
  SCHEMA
    <field> TEXT [NOSTEM] [NOTOKENIZE] [FROM <source_field>]
    | <field> <U64|I64|F64|BOOL|DATE> [FAST] [FROM <source_field>]
    | <field> <KEYWORD|FACET> [FROM <source_field>]
    [...]
```

## Arguments

| Argument | Description |
| --- | --- |
| `ON` | Type of Redis value to index: `JSON`, `HASH`, or `STRING`. A `STRING` value must contain a JSON object. |
| `PREFIX` | One or more key prefixes. Prefixes in the same index cannot be duplicates or overlap one another. |
| `LANGUAGE` | Stemming language for `TEXT` fields. Defaults to `english`. Supported values are `arabic`, `danish`, `dutch`, `english`, `finnish`, `french`, `german`, `greek`, `hungarian`, `italian`, `norwegian`, `portuguese`, `romanian`, `russian`, `spanish`, `swedish`, `tamil`, and `turkish`. |
| `SKIPINITIALSCAN` | Create the index without scanning existing keys. Later writes are still indexed; use [`SEARCH.REINDEX`](/docs/redis/commands/search/search-reindex) to add the current matching data. |
| `EXISTSOK` | Return `0` when an index with the same data type, prefixes, and schema already exists. A configuration mismatch returns an error. |
| `SCHEMA` | One or more field definitions. `SCHEMA` must be the final top-level clause. |

### Schema field options

| Option | Valid field types | Description |
| --- | --- | --- |
| `FAST` | `U64`, `I64`, `F64`, `BOOL`, `DATE` | Store the field for operations such as sorting and aggregations. Score functions accept `FAST` fields of type `U64`, `I64`, or `F64`. |
| `NOSTEM` | `TEXT` | Index text without stemming words to their roots. |
| `NOTOKENIZE` | `TEXT` | Index the entire value as one token. |
| `FROM <source_field>` | All field types | Read the value from a different document field or nested dot path while exposing it under `<field>` in the index. |

## Response

Returns `1` when the index is created. With `EXISTSOK`, returns `0` if the existing index has the same data type, prefixes, and schema. Returns an error for a different configuration or when `<name>` is already used by a non-index Redis key.

## Examples

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SEARCH.CREATE products ON JSON PREFIX 1 product: SCHEMA name TEXT price F64 FAST inStock BOOL
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis, s } from "@upstash/redis";

const redis = Redis.fromEnv();

const products = await redis.search.createIndex({
  name: "products",
  dataType: "json",
  prefix: "product:",
  schema: s.object({
    name: s.string(),
    price: s.number("F64"),
    inStock: s.boolean(),
  }),
});
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()

products = redis.search.create_index(
    name="products",
    data_type="json",
    prefixes="product:",
    schema={
        "name": "TEXT",
        "price": {"type": "F64", "fast": True},
        "inStock": "BOOL",
    },
)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import IORedis from "ioredis";
import { createSearch, s } from "@upstash/search-ioredis";

const redis = new IORedis(process.env.REDIS_URL!);
const search = createSearch(redis);

const products = await search.createIndex({
  name: "products",
  dataType: "json",
  prefix: "product:",
  schema: s.object({
    name: s.string(),
    price: s.number("F64"),
    inStock: s.boolean(),
  }),
});
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";
import { createSearch, s } from "@upstash/search-redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const search = createSearch(client);

const products = await search.createIndex({
  name: "products",
  dataType: "json",
  prefix: "product:",
  schema: s.object({
    name: s.string(),
    price: s.number("F64"),
    inStock: s.boolean(),
  }),
});
```

</Accordion>

<Accordion title="curl">

```bash
curl -X POST https://YOUR_ENDPOINT.upstash.io \
  -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
  -d '["SEARCH.CREATE", "products", "ON", "JSON", "PREFIX", "1", "product:", "SCHEMA", "name", "TEXT", "price", "F64", "FAST", "inStock", "BOOL"]'
```

</Accordion>

</AccordionGroup>

# SEARCH.DESCRIBE
Source: https://upstash.com/docs/redis/commands/search/search-describe

Use `SEARCH.DESCRIBE` to return metadata about a search index.

The reply reports how the index is defined, the key type and prefixes it covers, and the fields in its schema with their types and options, along with its current state such as the number of indexed documents. It is the way to confirm what an index actually indexes before writing queries against it, and to check whether a schema change was applied.

See [Describing an Index](/docs/redis/search/index-management#describing-an-index) for the feature guide.

The command accepts an index name or alias. When an alias is used, the response describes its target and reports the target's index name.

## Syntax

```redis
SEARCH.DESCRIBE <name>
```

## Response

Returns index metadata as alternating field names and values. The response contains:

| Field | Description |
| --- | --- |
| `name` | The index name. |
| `type` | The indexed Redis value type: `HASH`, `JSON`, or `STRING`. |
| `prefixes` | Key prefixes included in the index. |
| `language` | Stemming language used by `TEXT` fields. |
| `indexSize` | Current index size in bytes. |
| `pendingUpdates` | Number of updates waiting to be indexed. |
| `schema` | Field definitions, including `FROM`, `NOSTEM`, `NOTOKENIZE`, and `FAST` options where present. |

```text
[
  "name", "products",
  "type", "JSON",
  "prefixes", ["product:"],
  "language", "english",
  "indexSize", 1234,
  "pendingUpdates", 0,
  "schema", [["name", "TEXT"], ["price", "F64", "FAST"]]
]
```

Returns `null` if the index does not exist.

## Examples

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SEARCH.DESCRIBE products
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const products = redis.search.index({ name: "products" });
const description = await products.describe();
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
products = redis.search.index(name="products")
description = products.describe()
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import IORedis from "ioredis";
import { createSearch } from "@upstash/search-ioredis";

const redis = new IORedis(process.env.REDIS_URL!);
const search = createSearch(redis);
const products = search.index({ name: "products" });
const description = await products.describe();
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";
import { createSearch } from "@upstash/search-redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const search = createSearch(client);
const products = search.index({ name: "products" });
const description = await products.describe();
```

</Accordion>

<Accordion title="curl">

```bash
curl -X POST https://YOUR_ENDPOINT.upstash.io \
  -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
  -d '["SEARCH.DESCRIBE", "products"]'
```

</Accordion>

</AccordionGroup>

# SEARCH.DROP
Source: https://upstash.com/docs/redis/commands/search/search-drop

Use `SEARCH.DROP` to remove a search index. The underlying Redis keys are not deleted.

Only the index and its internal structures go away, so the documents themselves stay exactly as they are and can be indexed again later by creating a new index over the same prefixes. Queries against the dropped name fail from then on.

See [Dropping an Index](/docs/redis/search/index-management#dropping-an-index) for the feature guide.

`SEARCH.DROP` does not resolve aliases: `<name>` is always treated as the literal index name. Aliases that point to a dropped index are not removed automatically.

## Syntax

```redis
SEARCH.DROP <name>
```

## Response

Returns `1` if the index was dropped or `0` if the index was not found.

## Examples

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SEARCH.DROP products
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const products = redis.search.index({ name: "products" });
const result = await products.drop();
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
products = redis.search.index(name="products")
result = products.drop()
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import IORedis from "ioredis";
import { createSearch } from "@upstash/search-ioredis";

const redis = new IORedis(process.env.REDIS_URL!);
const search = createSearch(redis);
const products = search.index({ name: "products" });
const result = await products.drop();
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";
import { createSearch } from "@upstash/search-redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const search = createSearch(client);
const products = search.index({ name: "products" });
const result = await products.drop();
```

</Accordion>

<Accordion title="curl">

```bash
curl -X POST https://YOUR_ENDPOINT.upstash.io \
  -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
  -d '["SEARCH.DROP", "products"]'
```

</Accordion>

</AccordionGroup>

# SEARCH.LISTALIASES
Source: https://upstash.com/docs/redis/commands/search/search-listaliases

Use `SEARCH.LISTALIASES` to return all aliases and the indexes they point to.

The reply pairs each alias with its target index name, which is how you check what an alias currently resolves to before or after swapping it. Aliases whose target no longer exists are still listed, since an alias may point at a name that has not been created yet.

See [Listing Aliases](/docs/redis/search/aliases#listing-aliases) for the feature guide.

## Syntax

```redis
SEARCH.LISTALIASES
```

## Response

Returns an unordered array of `[alias, index_name]` pairs, or an empty array if no aliases exist. The response can include aliases whose target index no longer exists. To list every index in the database, use [`SEARCH.LISTINDEXES`](/docs/redis/commands/search/search-listindexes).

The SDKs below decode the pairs into an object or dictionary that maps each alias to its index name.

## Examples

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SEARCH.LISTALIASES
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const aliases = await redis.search.alias.list();
// { products: "products-v2" }
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
aliases = redis.search.alias.list()
# {"products": "products-v2"}
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import IORedis from "ioredis";
import { createSearch } from "@upstash/search-ioredis";

const redis = new IORedis(process.env.REDIS_URL!);
const search = createSearch(redis);
const aliases = await search.alias.list();
// { products: "products-v2" }
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";
import { createSearch } from "@upstash/search-redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const search = createSearch(client);
const aliases = await search.alias.list();
// { products: "products-v2" }
```

</Accordion>

<Accordion title="curl">

```bash
curl -X POST https://YOUR_ENDPOINT.upstash.io \
  -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
  -d '["SEARCH.LISTALIASES"]'
```

</Accordion>

</AccordionGroup>

# SEARCH.LISTINDEXES
Source: https://upstash.com/docs/redis/commands/search/search-listindexes

Use `SEARCH.LISTINDEXES` to list search indexes in the database.

`MATCH` filters index names with a glob-style pattern, while `LIMIT` and `OFFSET` page through the reply, which keeps the command usable on a database with many indexes. Only index names are returned; use [`SEARCH.DESCRIBE`](/docs/redis/commands/search/search-describe) to see an index's schema and settings.

See [Listing Indexes](/docs/redis/search/index-management#listing-indexes) for the feature guide.

## Syntax

```redis
SEARCH.LISTINDEXES [MATCH <pattern>] [LIMIT <count>] [OFFSET <offset>]
```

## Arguments

| Argument | Description | Default |
| --- | --- | --- |
| `MATCH` | Filter index names by a glob-style pattern, such as `product*`. | All indexes |
| `LIMIT` | Maximum number of indexes to return. `0` returns all indexes. | `0` (all indexes) |
| `OFFSET` | Number of indexes to skip for pagination. | `0` |

## Response

Returns one entry per index, sorted by index name. Each entry is a map containing the index `name` and its backing `type` (`HASH`, `JSON`, or `STRING`). With RESP2, each map is encoded as an array of alternating keys and values.

```text
[
  ["name", "productIdx", "type", "HASH"],
  ["name", "profileIdx", "type", "JSON"],
  ["name", "sessionIdx", "type", "STRING"]
]
```

## Examples

<Note>
  The high-level Search APIs do not currently expose `SEARCH.LISTINDEXES`. Use each client's generic command method for this command.
</Note>

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SEARCH.LISTINDEXES MATCH 'product*' LIMIT 10 OFFSET 0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const indexes = await redis.exec<string[][]>([
  "SEARCH.LISTINDEXES",
  "MATCH",
  "product*",
  "LIMIT",
  10,
  "OFFSET",
  0,
]);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
indexes = redis.execute([
    "SEARCH.LISTINDEXES",
    "MATCH",
    "product*",
    "LIMIT",
    10,
    "OFFSET",
    0,
])
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import IORedis from "ioredis";

const redis = new IORedis(process.env.REDIS_URL!);
const indexes = await redis.call(
  "SEARCH.LISTINDEXES",
  "MATCH",
  "product*",
  "LIMIT",
  10,
  "OFFSET",
  0,
);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const indexes = await client.sendCommand([
  "SEARCH.LISTINDEXES",
  "MATCH",
  "product*",
  "LIMIT",
  "10",
  "OFFSET",
  "0",
]);
```

</Accordion>

<Accordion title="curl">

```bash
curl -X POST https://YOUR_ENDPOINT.upstash.io \
  -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
  -d '["SEARCH.LISTINDEXES", "MATCH", "product*", "LIMIT", "10", "OFFSET", "0"]'
```

</Accordion>

</AccordionGroup>

# SEARCH.QUERY
Source: https://upstash.com/docs/redis/commands/search/search-query

Use `SEARCH.QUERY` to search for documents matching a JSON filter.

The filter is a JSON object naming index fields and the values to match, so `'{"name": "headphones", "inStock": true}'` combines conditions with an implicit AND. Text fields are matched with the analysis configured in the schema while other types are matched exactly, and operators such as `$fuzzy`, `$prefix`, `$range`, `$or`, and `$mustNot` cover the cases where plain field matching is not enough. Querying an index that does not exist returns null.

Results come back ordered by relevance score by default. `ORDERBY` sorts by a `FAST` field instead, `LIMIT` and `OFFSET` page through the matches, `SELECT` and `NOCONTENT` cut the payload down to the fields you need, `HIGHLIGHT` wraps the matched terms in tags for display, and `SCOREFUNC` blends numeric fields such as popularity or recency into the relevance score.

See [Querying and filtering](/docs/redis/search/querying) for the full filter syntax and worked examples, and [`SEARCH.COUNT`](/docs/redis/commands/search/search-count) when you only need the number of matches.

## Syntax

```redis
SEARCH.QUERY <name> '<query>'
  [LIMIT <count>]
  [OFFSET <offset>]
  [ORDERBY <field> [ASC|DESC]]
  [SELECT <count> <field> [<field> ...]]
  [NOCONTENT]
  [HIGHLIGHT FIELDS <count> <field> [<field> ...] [TAGS <open> <close>]]
  [SCOREFUNC
    FIELDVALUE <field>
      [MODIFIER <NONE|LOG|LOG1P|LOG2P|LN|LN1P|LN2P|SQUARE|SQRT|RECIPROCAL>]
      [FACTOR <number>]
      [MISSING <number>]
    [FIELDVALUE ...]
    [SCOREMODE <SUM|MULTIPLY|REPLACE>]
    [COMBINEMODE <SUM|MULTIPLY>]]
```

## Arguments

| Argument | Description | Default |
| --- | --- | --- |
| `LIMIT` | Maximum number of results to return. Must be between 1 and 1000. | `10` |
| `OFFSET` | Number of results to skip for pagination. Must be between 0 and 10,000. | `0` |
| `ORDERBY` | Sort by a `FAST` field. The direction defaults to `DESC` when omitted. | Relevance score, descending |
| `SELECT` | Return only the specified number of document fields. When the schema uses `FROM`, specify the source document field rather than its index alias. | All fields |
| `NOCONTENT` | Return keys and scores without document content. | Disabled |
| `HIGHLIGHT` | Wrap matching terms in tags. The default tags are `<em>` and `</em>`. | Disabled |
| `SCOREFUNC` | Adjust relevance scores using one or more `FAST` numeric fields. `FACTOR` defaults to `1`, `MISSING` to `0`, `MODIFIER` to `NONE`, `SCOREMODE` to `SUM`, and `COMBINEMODE` to `SUM`. | Disabled |
<Warning>
  `NOCONTENT` cannot be combined with `SELECT` or `HIGHLIGHT`. `SCOREFUNC` cannot be combined with `ORDERBY`. Inside `MULTI` or `EVAL`, the command requires `NOCONTENT`.
</Warning>

See [Querying and filtering](/docs/redis/search/querying) for the JSON filter operators and detailed query examples.

## Response

Returns an array of `[key, score, content]` results, or `null` if the index does not exist:

* `key` is the Redis key of the matching document.
* `score` is the floating-point relevance score.
* `content` is an array of field-value pairs. JSON indexes return `[["$", "<json_string>"]]`; hash indexes return `[["field", "value"], ...]`.

When `NOCONTENT` is used, each result is `[key, score]`. When `SELECT` is used, only fields that exist in the document appear in the content.

```text
[
  ["key1", 1.25, [["$", "{\"name\":\"...\"}"]]],
  ["key2", 0.75, [["$", "{\"name\":\"...\"}"]]]
]
```

## Examples

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SEARCH.QUERY products '{"name": "wireless"}' LIMIT 10 OFFSET 0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis, s } from "@upstash/redis";

const redis = Redis.fromEnv();
const products = redis.search.index({
  name: "products",
  schema: s.object({ name: s.string() }),
});

const results = await products.query({
  filter: { name: "wireless" },
  limit: 10,
  offset: 0,
});
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
products = redis.search.index(name="products")

results = products.query(
    filter={"name": "wireless"},
    limit=10,
    offset=0,
)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import IORedis from "ioredis";
import { createSearch, s } from "@upstash/search-ioredis";

const redis = new IORedis(process.env.REDIS_URL!);
const search = createSearch(redis);
const products = search.index({
  name: "products",
  schema: s.object({ name: s.string() }),
});

const results = await products.query({
  filter: { name: "wireless" },
  limit: 10,
  offset: 0,
});
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";
import { createSearch, s } from "@upstash/search-redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const search = createSearch(client);
const products = search.index({
  name: "products",
  schema: s.object({ name: s.string() }),
});

const results = await products.query({
  filter: { name: "wireless" },
  limit: 10,
  offset: 0,
});
```

</Accordion>

<Accordion title="curl">

```bash
curl -X POST https://YOUR_ENDPOINT.upstash.io \
  -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
  -d '["SEARCH.QUERY", "products", "{\"name\": \"wireless\"}", "LIMIT", "10", "OFFSET", "0"]'
```

</Accordion>

</AccordionGroup>

# SEARCH.REINDEX
Source: https://upstash.com/docs/redis/commands/search/search-reindex

Use `SEARCH.REINDEX` to rebuild an existing index from the current Redis keys that match its data type and prefixes. It is especially useful after creating an index with `SKIPINITIALSCAN`.

See [Index Management](/docs/redis/search/index-management) for the feature guide to creating and managing indexes.

`SEARCH.REINDEX` accepts an index name or alias. The index definition is preserved while its searchable contents are replaced with a fresh scan.

<Warning>
  Reindexing scans all matching keys and can be expensive for large datasets.
</Warning>

## Syntax

```redis
SEARCH.REINDEX <name>
```

## Response

Returns `1` when the index is rebuilt or `0` if the index does not exist.

## Examples

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SEARCH.REINDEX products
```

</Accordion>

<Accordion title="curl">

```bash
curl -X POST https://YOUR_ENDPOINT.upstash.io \
  -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
  -d '["SEARCH.REINDEX", "products"]'
```

</Accordion>

</AccordionGroup>

# SEARCH.WAITINDEXING
Source: https://upstash.com/docs/redis/commands/search/search-waitindexing

Use `SEARCH.WAITINDEXING` to block until all pending index updates are processed and visible to queries.

See [Waiting for Indexing](/docs/redis/search/index-management#waiting-for-indexing) for guidance on when to wait for index updates.

The command accepts an index name or alias and waits for every update queued before the command began. Writes that arrive later may require another call.

## Syntax

```redis
SEARCH.WAITINDEXING <name>
```

## Important points

<Warning>
  Do not call `SEARCH.WAITINDEXING` after every write. Batch updates are necessary for optimal indexing performance. Use this command only when you need to ensure queries reflect recent changes, such as in tests or CI pipelines.
</Warning>

## Response

Returns `1` when indexing is complete or `0` if the index was not found.

## Examples

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SEARCH.WAITINDEXING products
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const products = redis.search.index({ name: "products" });
const result = await products.waitIndexing();
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
products = redis.search.index(name="products")
result = products.wait_indexing()
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import IORedis from "ioredis";
import { createSearch } from "@upstash/search-ioredis";

const redis = new IORedis(process.env.REDIS_URL!);
const search = createSearch(redis);
const products = search.index({ name: "products" });
const result = await products.waitIndexing();
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";
import { createSearch } from "@upstash/search-redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const search = createSearch(client);
const products = search.index({ name: "products" });
const result = await products.waitIndexing();
```

</Accordion>

<Accordion title="curl">

```bash
curl -X POST https://YOUR_ENDPOINT.upstash.io \
  -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
  -d '["SEARCH.WAITINDEXING", "products"]'
```

</Accordion>

</AccordionGroup>

# ACL CAT
Source: https://upstash.com/docs/redis/commands/server/acl-cat

Use `ACL CAT` to discover the category names usable in [`ACL SETUSER`](/docs/redis/commands/server/acl-setuser) rules such as `+@read` or `-@dangerous`.

Called without an argument, it returns every category name the server knows, for example `read`, `write`, `keyspace`, `dangerous`, `admin`, `scripting`, and the categories for individual data types such as `string`, `hash`, or `json`. Called with a category name, it instead returns the commands that belong to that category, which is the fastest way to check exactly what a rule like `+@json` grants before applying it to a user.

## Syntax

```redis
ACL CAT [category]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `category` | No | No | When given, list the commands in this category instead of listing category names. |

## Important points

* An unknown category name returns an error rather than an empty list.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk strings |
| RESP3 | Array of bulk strings |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ACL CAT read
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.acl("CAT", "read");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.aclCat("read");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.acl_cat("read")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ACLCatArgs(context.Background(), &redis.ACLCatArgs{
        Category: "read",
    }).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.aclCat("read");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.acl_cat_categoryname("read")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ACL DELUSER
Source: https://upstash.com/docs/redis/commands/server/acl-deluser

Use `ACL DELUSER` to remove one or more ACL users and close any connections currently authenticated as them.

Multiple usernames can be deleted in a single call. The reply is the number of users that actually existed and were removed, which can be lower than the number of names passed in.

## Syntax

```redis
ACL DELUSER <username> [username ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `username` | Yes | Yes | Name of an ACL user to delete. |

## Important points

* The `default` user cannot be deleted; the command returns an error if it is included.
* Deleting a user immediately terminates any of its active connections and subscriptions.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ACL DELUSER app
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.acl("DELUSER", "app");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.aclDelUser("app");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.acl_deluser("app")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

<Note>
  `ACLDelUser` accepts a single username per call. To delete more than one user, call it once per username.
</Note>

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ACLDelUser(context.Background(), "app").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.aclDelUser("app");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.acl_deluser(&["app"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ACL GENPASS
Source: https://upstash.com/docs/redis/commands/server/acl-genpass

Use `ACL GENPASS` to generate a random, hex-encoded password suitable for a new ACL user.

The optional `bits` argument controls the strength of the generated password, rounded up to the nearest multiple of 8, and defaults to 256 bits when omitted. This only generates the password string; it does not create or update a user. To actually authenticate an ACL user over the REST API, generate credentials with [`ACL GENTOKEN`](/docs/redis/commands/server/acl-gentoken) instead, since a plain password from `GENPASS` cannot be passed to [`ACL SETUSER`](/docs/redis/commands/server/acl-setuser).

## Syntax

```redis
ACL GENPASS [bits]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `bits` | No | No | Password strength in bits, from 1 to 4096. Defaults to 256 and is rounded up to a multiple of 8. |

## Important points

* The returned password is not stored anywhere; it is only generated and returned.
* This value cannot be added to a user with `ACL SETUSER ... >password`. Use [`ACL GENTOKEN`](/docs/redis/commands/server/acl-gentoken) to create credentials that `SETUSER` will accept.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string |
| RESP3 | Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ACL GENPASS
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.acl("GENPASS");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.aclGenPass();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.acl_genpass()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ACLGenPass(context.Background(), 0).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.aclGenPass();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.acl_genpass()?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ACL GENTOKEN
Source: https://upstash.com/docs/redis/commands/server/acl-gentoken

Use `ACL GENTOKEN` to create the credential you need before you can give an ACL user a password.

[`ACL SETUSER`](/docs/redis/commands/server/acl-setuser) does not accept arbitrary passwords with `>password`: the value must come from `GENTOKEN`. Call it with just a username to have a strong password generated for you, or supply your own password to wrap instead. Either way, the reply is a token string that does two things at once: pass it to `SETUSER` as `>token` to set the user's password, and later use it directly as `UPSTASH_REDIS_REST_TOKEN`, or exchange it (or the password it wraps) for a fresh one with [`ACL RESTTOKEN`](/docs/redis/commands/server/acl-resttoken). `GENTOKEN` is an Upstash extension.

## Syntax

```redis
ACL GENTOKEN <username> [password]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `username` | Yes | No | ACL user this token will belong to. The user does not need to exist yet. |
| `password` | No | No | Password to wrap in the token. When omitted, a strong random password is generated instead. |

## Important points

* A supplied `password` is checked for minimum entropy and rejected if too weak.
* This only generates a token; it does not create or modify the user. Pass the result to `ACL SETUSER <username> >token` to actually set it as the user's password.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string |
| RESP3 | Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ACL GENTOKEN app
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.acl("GENTOKEN", "app");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sendCommand(["ACL", "GENTOKEN", "app"]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.execute_command("ACL", "GENTOKEN", "app")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Do(context.Background(), "ACL", "GENTOKEN", "app").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import java.nio.charset.StandardCharsets;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.commands.ProtocolCommand;

ProtocolCommand command = () -> "ACL".getBytes(StandardCharsets.UTF_8);
try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sendCommand(command, "GENTOKEN", "app");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("ACL");
    command.arg("GENTOKEN");
    command.arg("app");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ACL GETUSER
Source: https://upstash.com/docs/redis/commands/server/acl-getuser

Use `ACL GETUSER` to inspect the resulting permissions of a single ACL user, broken out into structured fields instead of the raw rule string that [`ACL LIST`](/docs/redis/commands/server/acl-list) returns.

The reply describes the user's on/off and other flags, its passwords, the commands it can call as a single space-separated permission string, the key patterns it can access, and the channel patterns it can publish to or subscribe on. A username that does not exist returns a null reply rather than an error, which makes this command a convenient way to check whether a user exists before creating or modifying it.

## Syntax

```redis
ACL GETUSER <username>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `username` | Yes | No | ACL user to inspect. |

## Important points

* Returns null when the given username does not exist, instead of an error.
* The reply has five fields: `flags`, `passwords`, `commands`, `keys`, and `channels`. Standard Redis 7+ also returns a `selectors` field; this deployment does not support selectors, so client libraries that expect one will simply receive an empty or undefined value for it.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Flat array of alternating field names and values, with nested arrays for `flags`, `passwords`, `keys`, and `channels`; or Null array |
| RESP3 | Map of five fields, with a Set for `flags` and Arrays for `passwords`, `keys`, and `channels`; or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ACL GETUSER app
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.acl("GETUSER", "app");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.aclGetUser("app");
console.log(result);
// { flags: [...], passwords: [...], commands: '...', keys: [...], channels: [...], selectors: undefined }
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.acl_getuser("app")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

<Note>
  go-redis has no typed `ACLGetUser` method; call `ACL GETUSER` through `Do`.
</Note>

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Do(context.Background(), "ACL", "GETUSER", "app").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.aclGetUser("app");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.acl_getuser("app")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ACL LIST
Source: https://upstash.com/docs/redis/commands/server/acl-list

Use `ACL LIST` to see every configured user together with their full rule set, formatted the same way a Redis ACL config file would describe them.

Each entry is a single string starting with `user <username>`, followed by its on/off flag, passwords, key patterns, channel patterns, and command permissions. This is the quickest way to audit every user's effective permissions at once; to inspect a single user as structured fields instead, use [`ACL GETUSER`](/docs/redis/commands/server/acl-getuser).

## Syntax

```redis
ACL LIST
```

## Arguments

This command takes no arguments.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk strings |
| RESP3 | Array of bulk strings |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ACL LIST
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.acl("LIST");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.aclList();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.acl_list()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ACLList(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.aclList();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.acl_list()?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ACL LOAD
Source: https://upstash.com/docs/redis/commands/server/acl-load

Use `ACL LOAD` to make sure the node you are connected to has the latest ACL users and rules.

Standard Redis reads `ACL LOAD` from an `aclfile` on disk. This deployment has no such file: instead, `ACL LOAD` refreshes this node's in-memory copy of the ACL definitions from the primary, which is useful right after a failover or when reconnecting to a node that may be slightly behind. Changes made with [`ACL SETUSER`](/docs/redis/commands/server/acl-setuser) and [`ACL DELUSER`](/docs/redis/commands/server/acl-deluser) are already propagated to replicas automatically, so `LOAD` is rarely needed in normal operation.

## Syntax

```redis
ACL LOAD
```

## Arguments

This command takes no arguments.

## Important points

* There is no ACL file to load from; this refreshes the node's ACL state from the primary instead.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ACL LOAD
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.acl("LOAD");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.aclLoad();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.acl_load()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

<Note>
  go-redis has no typed `ACLLoad` method; call `ACL LOAD` through `Do`.
</Note>

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Do(context.Background(), "ACL", "LOAD").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.aclLoad();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    connection.acl_load()?;
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ACL LOG
Source: https://upstash.com/docs/redis/commands/server/acl-log

Use `ACL LOG` to inspect recent authentication and permission failures, and `ACL LOG RESET` to clear it.

On standard Redis this is where to look when a client is unexpectedly denied: each entry records the reason, the username, and the command or key involved. This deployment does not yet record ACL failures, so `ACL LOG` currently always returns an empty array regardless of the optional count, while `ACL LOG RESET` still succeeds.

## Syntax

```redis
ACL LOG [count | RESET]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `count` | No | No | Maximum number of log entries to return. |
| `RESET` | No | No | Clear the log instead of reading it. |

## Important points

* `ACL LOG` currently always returns an empty array; this deployment does not yet track authentication or permission failures.
* `ACL LOG RESET` still returns `OK`.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array (currently always empty); or Simple string `OK` for `RESET` |
| RESP3 | Array (currently always empty); or Simple string `OK` for `RESET` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ACL LOG
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.acl("LOG");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.aclLog();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.acl_log()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ACLLog(context.Background(), 10).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.aclLog();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.acl_log(10)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ACL RESTTOKEN
Source: https://upstash.com/docs/redis/commands/server/acl-resttoken

Use `ACL RESTTOKEN` to get a REST API token for an ACL user you already created, given that user's username and password.

The password is the same value used with `>password` in [`ACL SETUSER`](/docs/redis/commands/server/acl-setuser), which is itself a value produced by [`ACL GENTOKEN`](/docs/redis/commands/server/acl-gentoken); passing the full token string that `GENTOKEN` returned also works. The result is a bearer token scoped to that user's permissions that can be set as `UPSTASH_REDIS_REST_TOKEN`, which is how a database user created for TCP access is also given REST API access. `RESTTOKEN` is an Upstash extension.

## Syntax

```redis
ACL RESTTOKEN <username> <password>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `username` | Yes | No | Existing ACL user to generate a REST token for. |
| `password` | Yes | No | That user's password, as set with `>password` in `ACL SETUSER`. |

## Important points

* Returns an error if the username does not exist or the password does not match.
* The returned token carries the same permissions as the underlying user; revoking or narrowing the user with `ACL SETUSER` also affects every token issued for it.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string |
| RESP3 | Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ACL RESTTOKEN app AQIDBAUGBwgJCgsMDQ4PEBESExQVFhc
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.acl("RESTTOKEN", "app", "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhc");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sendCommand(["ACL", "RESTTOKEN", "app", "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhc"]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.execute_command("ACL", "RESTTOKEN", "app", "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhc")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Do(context.Background(), "ACL", "RESTTOKEN", "app", "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhc").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import java.nio.charset.StandardCharsets;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.commands.ProtocolCommand;

ProtocolCommand command = () -> "ACL".getBytes(StandardCharsets.UTF_8);
try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sendCommand(command, "RESTTOKEN", "app", "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhc");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("ACL");
    command.arg("RESTTOKEN");
    command.arg("app");
    command.arg("AQIDBAUGBwgJCgsMDQ4PEBESExQVFhc");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ACL SAVE
Source: https://upstash.com/docs/redis/commands/server/acl-save

Use `ACL SAVE` to make sure this node's ACL users and rules have been propagated to its replicas.

Standard Redis writes `ACL SAVE` to an `aclfile` on disk. This deployment has no such file: instead, `ACL SAVE` pushes the current primary's in-memory ACL definitions out to its replicas. Changes made with [`ACL SETUSER`](/docs/redis/commands/server/acl-setuser) and [`ACL DELUSER`](/docs/redis/commands/server/acl-deluser) are already propagated automatically, so `SAVE` is rarely needed in normal operation.

## Syntax

```redis
ACL SAVE
```

## Arguments

This command takes no arguments.

## Important points

* There is no ACL file to save to; this pushes the primary's ACL state to its replicas instead.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ACL SAVE
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.acl("SAVE");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.aclSave();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.acl_save()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

<Note>
  go-redis has no typed `ACLSave` method; call `ACL SAVE` through `Do`.
</Note>

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Do(context.Background(), "ACL", "SAVE").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.aclSave();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    connection.acl_save()?;
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ACL SETUSER
Source: https://upstash.com/docs/redis/commands/server/acl-setuser

Use `ACL SETUSER` to create a new ACL user or change the rules of an existing one.

Rules are applied left to right in a single call: they enable or disable the user (`on`, `off`), grant or revoke access to key patterns (`~pattern`), channel patterns (`&pattern`), commands and categories (`+get`, `-@admin`), and manage passwords. Because rules are cumulative, calling `SETUSER` again only adds to or removes from what a user already has; use `reset` to start over from a clean slate.

## Syntax

```redis
ACL SETUSER <username> [rule ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `username` | Yes | No | ACL user to create or modify. |
| `rule` | No | Yes | One rule token, applied in order. See the table below. |

| Rule | Effect |
| --- | --- |
| `on` / `off` | Enable or disable authentication for the user. |
| `>token` | Add a password. `token` must be a value returned by [`ACL GENTOKEN`](/docs/redis/commands/server/acl-gentoken), not an arbitrary string. |
| `<token` | Remove a password previously added this way. |
| `!hash` | Remove a password by its 64-character lowercase SHA-256 hash. |
| `resetpass` | Remove every password set on the user. |
| `~pattern` | Grant access to keys matching `pattern`. |
| `allkeys` | Alias for `~*`. |
| `resetkeys` | Remove every key pattern granted so far. |
| `&pattern` | Grant access to pub/sub channels matching `pattern`. |
| `allchannels` | Alias for `&*`. |
| `resetchannels` | Remove every channel pattern granted so far. |
| `+command` / `-command` | Grant or revoke a single command. |
| `+@category` / `-@category` | Grant or revoke every command in a category; see [`ACL CAT`](/docs/redis/commands/server/acl-cat). |
| `allcommands` | Alias for `+@all`. |
| `nocommands` | Alias for `-@all`. |
| `reset` | Reset the user to its just-created state: `resetpass`, `resetkeys`, `resetchannels`, `off`, `nocommands`. |

## Important points

* This command can expose administrative information or make a broad destructive change. Restrict it to trusted code paths.
* `nopass` is rejected. Every user must have at least one password; there is no way to allow authentication with any password.
* Plain-text passwords (`>password`) and pre-hashed passwords (`#hash`) are rejected. Passwords must be generated with [`ACL GENTOKEN`](/docs/redis/commands/server/acl-gentoken) and added with `>token`; this is what lets the same credential authenticate on both the TCP and REST endpoints.
* Subcommand-scoped rules such as `+client|list` are not supported and return an error.
* The `default` user cannot be modified; the command returns an error if it is the target.
* Changes take effect immediately on new and existing connections, so a rule that narrows access can lock out a running application. Check with [`ACL GETUSER`](/docs/redis/commands/server/acl-getuser) before applying it broadly.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ACL SETUSER app on ~cache:* +get +set
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.acl("SETUSER", "app", "on", "~cache:*", "+get", "+set");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.aclSetUser("app", ["on", "~cache:*", "+get", "+set"]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.acl_setuser(
    "app",
    enabled=True,
    keys=["cache:*"],
    commands=["+get", "+set"],
)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ACLSetUser(context.Background(), "app", "on", "~cache:*", "+get", "+set").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.aclSetUser("app", "on", "~cache:*", "+get", "+set");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;
use redis::acl::Rule;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let rules = [
        Rule::On,
        Rule::Pattern("cache:*".to_string()),
        Rule::AddCommand("get".to_string()),
        Rule::AddCommand("set".to_string()),
    ];
    connection.acl_setuser_rules("app", &rules)?;
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ACL USERS
Source: https://upstash.com/docs/redis/commands/server/acl-users

Use `ACL USERS` to list the usernames of every user currently configured on the database, including `default`.

This returns only the names, with no rules or permissions attached. Use [`ACL GETUSER`](/docs/redis/commands/server/acl-getuser) on a specific username to inspect what it can do, or [`ACL LIST`](/docs/redis/commands/server/acl-list) to get every user's full rule set in one call.

## Syntax

```redis
ACL USERS
```

## Arguments

This command takes no arguments.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk strings |
| RESP3 | Array of bulk strings |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ACL USERS
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.acl("USERS");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.aclUsers();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.acl_users()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ACLUsers(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.aclUsers();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.acl_users()?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ACL WHOAMI
Source: https://upstash.com/docs/redis/commands/server/acl-whoami

Use `ACL WHOAMI` to find out which ACL user the current connection is authenticated as.

This is useful in shared code paths where the same client library is reused across users or environments, and you want to confirm which credentials actually ended up on the connection rather than assuming the ones you configured.

## Syntax

```redis
ACL WHOAMI
```

## Arguments

This command takes no arguments.

## Important points

* The connection must be authenticated. An unauthenticated connection returns an error instead of a username.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string |
| RESP3 | Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ACL WHOAMI
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.acl("WHOAMI");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.aclWhoAmI();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.acl_whoami()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ACLWhoAmI(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.aclWhoAmI();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.acl_whoami()?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# COMMAND
Source: https://upstash.com/docs/redis/commands/server/command

Use `COMMAND` to get metadata about the commands the server supports, such as their arity, flags, and key positions. Client libraries call it during connection setup to discover how to route commands and where their keys are.

The current Upstash deployment accepts the command for client compatibility but returns an empty array rather than exposing the server command table, so clients fall back to their built-in command metadata.

## Syntax

```redis
COMMAND
```

## Arguments

This command takes no arguments.

## Important points

* The current Upstash deployment accepts `COMMAND` for client compatibility but returns an empty array; it does not expose the server command table.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Empty array |
| RESP3 | Empty array |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
COMMAND
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("COMMAND");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.command();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.command()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Command(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.command();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("COMMAND");

    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# CONFIG GET
Source: https://upstash.com/docs/redis/commands/server/config-get

Use `CONFIG GET` to read runtime configuration parameters.

Parameter names may contain glob-style patterns, so one call can fetch a family of settings, and the reply is a flat array of alternating names and values in both RESP2 and RESP3.

This deployment exposes only `notify-keyspace-events`, which controls the keyspace notifications published on pub/sub. Other names simply return no matching entries rather than an error, so check the reply for the parameter you asked about instead of assuming it was present.

## Syntax

```redis
CONFIG GET <parameter> [parameter ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `parameter` | Yes | Yes | One or more glob-style configuration parameter patterns. |

## Important points

* This command can expose administrative information or make a broad destructive change. Restrict it to trusted code paths.
* This deployment exposes only `notify-keyspace-events`. Other patterns return no matching entries.
* Both RESP2 and RESP3 return a flat array of alternating parameter names and values.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Flat array of alternating configuration names and values |
| RESP3 | Flat array of alternating configuration names and values |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
CONFIG GET notify-keyspace-events
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.config("GET", "notify-keyspace-events");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.configGet("notify-keyspace-events");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.config_get("notify-keyspace-events")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ConfigGet(context.Background(), "notify-keyspace-events").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.configGet("notify-keyspace-events");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("CONFIG");
    command.arg("GET");
    command.arg("notify-keyspace-events");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# CONFIG SET
Source: https://upstash.com/docs/redis/commands/server/config-set

Use `CONFIG SET` to change runtime configuration parameters without restarting.

This deployment permits changes only to `notify-keyspace-events`, which selects the keyspace and keyevent notifications the server publishes: the value is a set of flag characters, such as `KEA` for everything, or `Kg$` for generic and string keyspace events. Any other parameter returns an error.

Several parameter and value pairs can be given in one call, and they are processed from left to right, so if a later pair is rejected an earlier change may already have taken effect.

## Syntax

```redis
CONFIG SET <parameter> <value> [parameter value ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `parameter` | Yes | Yes, in pairs | Runtime configuration parameter to update. |
| `value` | Yes | Yes, in pairs | New value for the preceding parameter. |

## Important points

* This command can expose administrative information or make a broad destructive change. Restrict it to trusted code paths.
* This deployment permits changes only to `notify-keyspace-events`; any other parameter returns an error.
* Parameter/value pairs are processed from left to right. If a later pair is invalid, an earlier valid change may already have taken effect.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
CONFIG SET notify-keyspace-events Ex
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.config("SET", "notify-keyspace-events", "Ex");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.configSet("notify-keyspace-events", "Ex");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.config_set("notify-keyspace-events", "Ex")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ConfigSet(context.Background(), "notify-keyspace-events", "Ex").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.configSet("notify-keyspace-events", "Ex");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("CONFIG");
    command.arg("SET");
    command.arg("notify-keyspace-events");
    command.arg("Ex");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# DBSIZE
Source: https://upstash.com/docs/redis/commands/server/dbsize

Use `DBSIZE` to get the number of keys stored in the database.

The count covers keys of every type, and keys whose expiration has passed but that have not been removed yet may still be included, so treat the number as an accurate but momentary figure rather than an exact one. It is the quick way to see how a keyspace is growing, and unlike [`KEYS`](/docs/redis/commands/generic/keys) it does not build a list of key names to do so.

## Syntax

```redis
DBSIZE
```

## Arguments

This command takes no arguments.

## Important points

* This operation can inspect a large part of the database. Prefer cursor-based scans where possible and avoid unbounded use on hot paths.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
DBSIZE
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const keys = await redis.dbsize();
console.log(keys) // 20
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.dbsize()
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.dbsize();
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.dbSize();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.dbsize()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.DBSize(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.dbSize();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("DBSIZE");

    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# FLUSHALL
Source: https://upstash.com/docs/redis/commands/server/flushall

Use `FLUSHALL` to delete every key in every database.

Upstash exposes a single logical database, so `FLUSHALL` and [`FLUSHDB`](/docs/redis/commands/server/flushdb) have the same effect here. `ASYNC` frees the memory in a background thread, which returns faster on large datasets, while `SYNC` frees it before replying.

The deletion is immediate and irreversible: there is no confirmation step and nothing to undo. Keep it out of application code, run it only against disposable databases, and require an explicit operator action wherever tooling exposes it.

## Syntax

```redis
FLUSHALL [ASYNC | SYNC]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `(ASYNC \| SYNC)` | No | No | Choose one form: `ASYNC` (request asynchronous cleanup); `SYNC` (request synchronous cleanup). |

## Important points

* This command can expose administrative information or make a broad destructive change. Restrict it to trusted code paths.
* This operation can inspect a large part of the database. Prefer cursor-based scans where possible and avoid unbounded use on hot paths.
* This deletes the database contents. Test against a disposable database and require an explicit operator action in production tooling.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
FLUSHALL
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.flushall();
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.flushall()
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.flushall();
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.flushAll();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.flushall()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.FlushAll(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.flushAll();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.flushall()?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# FLUSHDB
Source: https://upstash.com/docs/redis/commands/server/flushdb

Use `FLUSHDB` to delete every key in the current database.

Since Upstash exposes a single logical database, this removes everything, exactly like [`FLUSHALL`](/docs/redis/commands/server/flushall). `ASYNC` frees the memory in a background thread and `SYNC` frees it before replying.

The deletion is immediate and irreversible. It is a convenient way to reset a test database between runs, and it should never be reachable from application code that runs against production data.

## Syntax

```redis
FLUSHDB [ASYNC | SYNC]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `(ASYNC \| SYNC)` | No | No | Choose one form: `ASYNC` (request asynchronous cleanup); `SYNC` (request synchronous cleanup). |

## Important points

* This command can expose administrative information or make a broad destructive change. Restrict it to trusted code paths.
* This operation can inspect a large part of the database. Prefer cursor-based scans where possible and avoid unbounded use on hot paths.
* This deletes the database contents. Test against a disposable database and require an explicit operator action in production tooling.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
FLUSHDB
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.flushdb();
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.flushdb()
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.flushdb();
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.flushDb();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.flushdb()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.FlushDB(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.flushDB();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.flushdb()?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# INFO
Source: https://upstash.com/docs/redis/commands/server/info

Use `INFO` to get statistics and information about the server.

The reply groups fields into sections such as `server`, `clients`, `memory`, `stats`, `replication`, and `keyspace`, and passing a section name limits the reply to that one. It is the usual source for operational numbers such as connected clients, memory in use, hit and miss counts, and the number of keys per database.

On Upstash, `default` and `all` return the same set of sections, and a section name that is not supported produces only its heading. The reply is a CRLF-delimited text report rather than a RESP map, so parse it defensively, since fields may be added over time.

## Syntax

```redis
INFO [section]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `section` | No | No | Section to return: `server`, `clients`, `memory`, `stats`, `cluster`, `keyspace`, `default`, or `all`. |

## Important points

* This command can expose administrative information or make a broad destructive change. Restrict it to trusted code paths.
* `default` and `all` return the same Upstash section set. Unsupported section names produce only a section heading.
* The reply is a CRLF-delimited text report, not a RESP map. Parse it defensively because fields may be added over time.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string |
| RESP3 | Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
INFO memory
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.info("memory");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.info("memory");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.info("memory")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Info(context.Background(), "memory").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.info("memory");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("INFO");
    command.arg("memory");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# MONITOR
Source: https://upstash.com/docs/redis/commands/server/monitor

Use `MONITOR` to stream every command the server processes, as it happens, to the current connection.

The connection turns into a continuous feed and stops accepting other commands, so it must be a dedicated one; closing it is how you stop monitoring. Each line carries a timestamp, the client that sent the command, and the command with its arguments.

It is a debugging tool: it shows the traffic of all clients, arguments included, so it can expose sensitive data, and the added work costs the server throughput while it is running. Use it briefly and against non-production data where possible.

## Syntax

```redis
MONITOR
```

## Arguments

This command takes no arguments.

## Important points

* This is a connection-oriented command and is available over native Redis TCP, not the stateless REST endpoint.
* This command can expose administrative information or make a broad destructive change. Restrict it to trusted code paths.
* MONITOR changes the TCP connection into a continuous command stream. Use a dedicated connection and close it to stop monitoring.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK`, then a stream of bulk strings, one per command the server executes |
| RESP3 | Simple string `OK`, then a stream of bulk strings, one per command the server executes |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
MONITOR
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
redis.monitor((error, monitor) => {
  if (error) throw error;
  monitor.on("monitor", (time, args, source, database) => {
    console.log({ time, args, source, database });
  });
});
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();

await client.monitor((command) => {
  console.log(command);
});
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
with client.monitor() as monitor:
    for command in monitor.listen():
        print(command)
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisMonitor;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  jedis.monitor(new JedisMonitor() {
    @Override
    public void onCommand(String command) {
      System.out.println(command);
    }
  });
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let packed = redis::cmd("MONITOR").get_packed_command();
    connection.send_packed_command(&packed)?;
    loop {
        println!("{:?}", connection.recv_response()?);
    }
}
```

</Accordion>

</AccordionGroup>

# Server commands
Source: https://upstash.com/docs/redis/commands/server/overview

<CardGroup cols={2}>
<Card title="ACL CAT" href="/redis/commands/server/acl-cat">List command categories</Card>
<Card title="ACL DELUSER" href="/redis/commands/server/acl-deluser">Delete one or more ACL users</Card>
<Card title="ACL GENPASS" href="/redis/commands/server/acl-genpass">Generate a random password</Card>
<Card title="ACL GENTOKEN" href="/redis/commands/server/acl-gentoken">Generate a REST token for SETUSER</Card>
<Card title="ACL GETUSER" href="/redis/commands/server/acl-getuser">Get the user's details</Card>
<Card title="ACL LIST" href="/redis/commands/server/acl-list">Show users details in config file format</Card>
<Card title="ACL LOAD" href="/redis/commands/server/acl-load">Reload ACL definitions from the primary node</Card>
<Card title="ACL LOG" href="/redis/commands/server/acl-log">Show the ACL log entries</Card>
<Card title="ACL RESTTOKEN" href="/redis/commands/server/acl-resttoken">Generate a REST token for an existing user</Card>
<Card title="ACL SAVE" href="/redis/commands/server/acl-save">Push ACL definitions to replica nodes</Card>
<Card title="ACL SETUSER" href="/redis/commands/server/acl-setuser">Create or modify a user</Card>
<Card title="ACL USERS" href="/redis/commands/server/acl-users">List all the registered usernames</Card>
<Card title="ACL WHOAMI" href="/redis/commands/server/acl-whoami">Return the current connection username</Card>
<Card title="COMMAND" href="/redis/commands/server/command">Get command metadata</Card>
<Card title="CONFIG GET" href="/redis/commands/server/config-get">Read runtime configuration</Card>
<Card title="CONFIG SET" href="/redis/commands/server/config-set">Change runtime configuration</Card>
<Card title="DBSIZE" href="/redis/commands/server/dbsize">Get number of keys in database</Card>
<Card title="FLUSHALL" href="/redis/commands/server/flushall">Delete all keys in all databases</Card>
<Card title="FLUSHDB" href="/redis/commands/server/flushdb">Delete all keys in current database</Card>
<Card title="INFO" href="/redis/commands/server/info">Get server statistics and information</Card>
<Card title="MONITOR" href="/redis/commands/server/monitor">Stream all commands received</Card>
<Card title="TIME" href="/redis/commands/server/time">Get current server time</Card>
</CardGroup>

# TIME
Source: https://upstash.com/docs/redis/commands/server/time

Use `TIME` to read the server's clock.

The reply has two values: the Unix time in seconds and the microseconds elapsed within the current second. Because it comes from the server, every client gets the same reference regardless of its own clock drift, which makes it the value to use when computing deadlines, measuring intervals, or writing timestamps that different machines must agree on.

## Syntax

```redis
TIME
```

## Arguments

This command takes no arguments.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Two-element array of bulk strings: Unix seconds and microseconds |
| RESP3 | Two-element array of bulk strings: Unix seconds and microseconds |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
TIME
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.time();
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.time()
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.time();
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.time();
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.time()
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Time(context.Background()).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.time();
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("TIME");

    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# Set commands
Source: https://upstash.com/docs/redis/commands/set/overview

<CardGroup cols={2}>
<Card title="SADD" href="/redis/commands/set/sadd">Add members to a set</Card>
<Card title="SCARD" href="/redis/commands/set/scard">Get set cardinality</Card>
<Card title="SDIFF" href="/redis/commands/set/sdiff">Get set difference</Card>
<Card title="SDIFFSTORE" href="/redis/commands/set/sdiffstore">Store set difference</Card>
<Card title="SINTER" href="/redis/commands/set/sinter">Get set intersection</Card>
<Card title="SINTERCARD" href="/redis/commands/set/sintercard">Get intersection cardinality</Card>
<Card title="SINTERSTORE" href="/redis/commands/set/sinterstore">Store set intersection</Card>
<Card title="SISMEMBER" href="/redis/commands/set/sismember">Check if member exists in set</Card>
<Card title="SMEMBERS" href="/redis/commands/set/smembers">Get all set members</Card>
<Card title="SMISMEMBER" href="/redis/commands/set/smismember">Check membership of several values</Card>
<Card title="SMOVE" href="/redis/commands/set/smove">Move member between sets</Card>
<Card title="SPOP" href="/redis/commands/set/spop">Remove and return random members</Card>
<Card title="SRANDMEMBER" href="/redis/commands/set/srandmember">Get random members</Card>
<Card title="SREM" href="/redis/commands/set/srem">Remove members from a set</Card>
<Card title="SSCAN" href="/redis/commands/set/sscan">Incrementally iterate set members</Card>
<Card title="SUNION" href="/redis/commands/set/sunion">Get set union</Card>
<Card title="SUNIONSTORE" href="/redis/commands/set/sunionstore">Store set union</Card>
</CardGroup>

# SADD
Source: https://upstash.com/docs/redis/commands/set/sadd

Use `SADD` to add one or more members to a set, creating it when the key does not exist.

A set holds unique, unordered members, so adding a member that is already there changes nothing, and the reply counts only the members that were actually new. That count is what makes the command a cheap deduplicator: a reply of `1` means this caller was the first to see the value, which is enough to decide whether to process an event or ignore it as a repeat.

## Syntax

```redis
SADD <key> <member> [<member> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<member>` | Yes | Yes | Member name. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SADD my-key member
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

// 3
await redis.sadd("key", "a", "b", "c");

// 0
await redis.sadd("key", "a", "b");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.sadd("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.sadd("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sAdd("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.sadd("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SAdd(context.Background(), "my-key", "member").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sadd("my-key", "member");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.sadd("my-key", "member")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SCARD
Source: https://upstash.com/docs/redis/commands/set/scard

Use `SCARD` to get the number of members in a set.

The reply is `0` when the key does not exist. The size is maintained by Redis rather than counted on demand, so the command is cheap on sets of any size, which makes it the right way to check how large a set has become before reading it with [`SMEMBERS`](/docs/redis/commands/set/smembers).

## Syntax

```redis
SCARD <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SCARD my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.sadd("key", "a", "b", "c");
const cardinality = await redis.scard("key");
console.log(cardinality); // 3
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.scard("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.scard("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sCard("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.scard("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SCard(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.scard("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.scard("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SDIFF
Source: https://upstash.com/docs/redis/commands/set/sdiff

Use `SDIFF` to get the members of the first set that are not present in any of the other sets.

Keys that do not exist count as empty sets, so a missing first key yields an empty result. The difference is computed on the fly and nothing is stored; use [`SDIFFSTORE`](/docs/redis/commands/set/sdiffstore) to keep the result.

It answers questions of the form "who is in A but not in B", such as users who signed up but never converted. The cost grows with the size of the sets involved, so on large sets prefer storing the result and reusing it.

## Syntax

```redis
SDIFF <key> [<key> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | Yes | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string members |
| RESP3 | Set of bulk-string members |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SDIFF set1 set2
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.sadd("set1", "a", "b", "c");
await redis.sadd("set2", "c", "d", "e");
const diff =  await redis.sdiff("set1", "set2");
console.log(diff); // ["a", "b"]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.sdiff("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.sdiff("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sDiff("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.sdiff(["my-key"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SDiff(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sdiff("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.sdiff(&["my-key"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SDIFFSTORE
Source: https://upstash.com/docs/redis/commands/set/sdiffstore

Use `SDIFFSTORE` to compute the difference between sets and store it in another key.

The destination holds the members of the first source set that are absent from all the others. It is overwritten if it already exists, and deleted when the result is empty. The reply is the number of members stored.

Storing rather than returning the result is what you want when the difference is large or is used more than once, since the destination is an ordinary set that can be paged through or fed into further set operations.

## Syntax

```redis
SDIFFSTORE <destination> <key> [<key> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<destination>` | Yes | No | Redis key used as destination. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SDIFFSTORE dest set1 set2
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.sadd("set1", "a", "b", "c");
await redis.sadd("set2", "c", "d", "e");
await redis.sdiff("dest", "set1", "set2");
console.log(diff); // ["a", "b"]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.sdiffstore("destination-key", "my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.sdiffstore("destination-key", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sDiffStore("destination-key", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.sdiffstore("destination-key", ["my-key"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SDiffStore(context.Background(), "destination-key", "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sdiffstore("destination-key", "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.sdiffstore("destination-key", &["my-key"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SINTER
Source: https://upstash.com/docs/redis/commands/set/sinter

Use `SINTER` to get the members present in every one of the given sets.

A key that does not exist counts as an empty set, which makes the whole intersection empty. The result is computed on each call and not stored; use [`SINTERSTORE`](/docs/redis/commands/set/sinterstore) to keep it, or [`SINTERCARD`](/docs/redis/commands/set/sintercard) when only the number of common members matters.

Intersections express "and" queries over sets, such as the users who are in both a campaign audience and a paying-customer set.

## Syntax

```redis
SINTER <key> [<key> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | Yes | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string members |
| RESP3 | Set of bulk-string members |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SINTER set1 set2
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.sadd("set1", "a", "b", "c");
await redis.sadd("set2", "c", "d", "e");
const intersection =  await redis.sinter("set1", "set2");
console.log(intersection); // ["c"]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.sinter("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.sinter("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sInter("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.sinter(["my-key"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SInter(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sinter("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.sinter(&["my-key"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SINTERCARD
Source: https://upstash.com/docs/redis/commands/set/sintercard

Use `SINTERCARD` to get the number of members that the given sets have in common, without building the intersection.

`<numkeys>` states how many keys follow. `LIMIT` stops counting once that many common members have been found and returns the limit, which turns an expensive full intersection into a cheap "do these sets share at least N members" test. `LIMIT 0` means no limit.

Since nothing is transferred beyond the count, this is much lighter than running [`SINTER`](/docs/redis/commands/set/sinter) and measuring the reply.

## Syntax

```redis
SINTERCARD <numkeys> <key> [<key> ...] [LIMIT <limit>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |
| `LIMIT <limit>` | No | No | Stop counting once this many members match; `0` means no limit. |

## Important points

* `numkeys` must equal the number of key arguments that immediately follow it.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SINTERCARD 1 my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.sintercard(["my-key"]);
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.sintercard("1", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sInterCard(["my-key"]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.sintercard(1, ["my-key"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SInterCard(context.Background(), 0, "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sintercard("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("SINTERCARD");
    command.arg("1");
    command.arg("my-key");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SINTERSTORE
Source: https://upstash.com/docs/redis/commands/set/sinterstore

Use `SINTERSTORE` to compute the intersection of sets and store it in another key.

The destination is overwritten if it exists and deleted when the intersection is empty, and the reply is the number of members stored. Materializing the result pays off when the same intersection is read repeatedly or feeds further set operations, since the destination is an ordinary set.

## Syntax

```redis
SINTERSTORE <destination> <key> [<key> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<destination>` | Yes | No | Redis key used as destination. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SINTERSTORE destination set1 set2
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.sadd("set1", "a", "b", "c");
await redis.sadd("set2", "c", "d", "e");
await redis.sinterstore("destination", "set1", "set2");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.sinterstore("destination-key", "my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.sinterstore("destination-key", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sInterStore("destination-key", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.sinterstore("destination-key", ["my-key"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SInterStore(context.Background(), "destination-key", "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sinterstore("destination-key", "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.sinterstore("destination-key", &["my-key"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SISMEMBER
Source: https://upstash.com/docs/redis/commands/set/sismember

Use `SISMEMBER` to check whether a value is a member of a set.

The reply is `1` when it is and `0` when it is not or the key does not exist. The check is a constant-time lookup and transfers nothing but the answer, which is why sets are the right structure for membership questions such as permission or blocklist checks. Use [`SMISMEMBER`](/docs/redis/commands/set/smismember) to test several values in one call.

## Syntax

```redis
SISMEMBER <key> <member>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<member>` | Yes | No | Member name. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: `1` if the member is in the set, `0` otherwise |
| RESP3 | Integer: `1` if the member is in the set, `0` otherwise |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SISMEMBER my-key member
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.sadd("set", "a", "b", "c");
const isMember =  await redis.sismember("set", "a");
console.log(isMember); // 1
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.sismember("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.sismember("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sIsMember("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.sismember("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SIsMember(context.Background(), "my-key", "member").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sismember("my-key", "member");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.sismember("my-key", "member")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SMEMBERS
Source: https://upstash.com/docs/redis/commands/set/smembers

Use `SMEMBERS` to get every member of a set.

A missing key returns an empty result, and members come back in no particular order, since sets are unordered. The whole set is transferred in one reply, so on large sets prefer [`SSCAN`](/docs/redis/commands/set/sscan), which walks the members in batches, and use [`SISMEMBER`](/docs/redis/commands/set/sismember) when you only want to test membership.

## Syntax

```redis
SMEMBERS <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string members |
| RESP3 | Array of bulk-string members |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SMEMBERS my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.sadd("set", "a", "b", "c");
const members =  await redis.smembers("set");
console.log(members); // ["a", "b", "c"]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.smembers("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.smembers("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sMembers("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.smembers("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SMembers(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.smembers("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.smembers("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SMISMEMBER
Source: https://upstash.com/docs/redis/commands/set/smismember

Use `SMISMEMBER` to check several values for membership in a set in one call.

The reply holds one `1` or `0` per value, in the order given, so it lines up with your input list. A missing key yields all zeros rather than an error. It replaces a series of [`SISMEMBER`](/docs/redis/commands/set/sismember) round trips, which matters when checking a batch of ids against a blocklist or an entitlement set.

## Syntax

```redis
SMISMEMBER <key> <member> [<member> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<member>` | Yes | Yes | Member name. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integers, one per queried member |
| RESP3 | Array of integers, one per queried member |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SMISMEMBER my-key member
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.sadd("set", "a", "b", "c");
const members =  await redis.smismember("set", ["a", "b", "d"]);
console.log(members); // [1, 1, 0]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.smismember("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.smismember("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.smIsMember("my-key", ["member"]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.smismember("my-key", ["member"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SMIsMember(context.Background(), "my-key", "member").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.smismember("my-key", "member");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.smismember("my-key", &["member"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SMOVE
Source: https://upstash.com/docs/redis/commands/set/smove

Use `SMOVE` to move a member from one set to another atomically.

The reply is `1` when the member was moved and `0` when it was not in the source set, in which case nothing changes. If the member already exists in the destination it is simply removed from the source. The destination is created when it does not exist, and the source is deleted when its last member leaves.

Because the removal and the insertion happen as one step, this is the safe way to move an item between states, such as from a pending set to a done set, without a window in which it is in both or in neither.

## Syntax

```redis
SMOVE <source> <destination> <member>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<source>` | Yes | No | Redis key used as source. |
| `<destination>` | Yes | No | Redis key used as destination. |
| `<member>` | Yes | No | Member name. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: `1` if the member was moved, `0` if it is not in the source set |
| RESP3 | Integer: `1` if the member was moved, `0` if it is not in the source set |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SMOVE source-key destination-key member
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.sadd("original", "a", "b", "c");
const moved =  await redis.smove("original", "destination", "a");
// moved:       1
// original:    ["b", "c"]
// destination: ["a"]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.smove("source-key", "destination-key", "member")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.smove("source-key", "destination-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sMove("source-key", "destination-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.smove("source-key", "destination-key", "member")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SMove(context.Background(), "source-key", "destination-key", "member").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.smove("source-key", "destination-key", "member");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.smove("source-key", "destination-key", "member")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SPOP
Source: https://upstash.com/docs/redis/commands/set/spop

Use `SPOP` to remove and return random members of a set.

Without a count one member is returned, or null when the key does not exist. With a count, up to that many distinct members are removed at once, capped at the size of the set, and the key is deleted when the last member is taken.

Since the read and the removal are atomic, no two callers can get the same member, which makes a set usable as a pool of unique items to hand out, such as invitation codes or shard assignments. Use [`SRANDMEMBER`](/docs/redis/commands/set/srandmember) when the members should stay in the set.

## Syntax

```redis
SPOP <key> [<count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<count>` | No | No | Number of members to pop. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, Bulk string, or array of bulk-string members |
| RESP3 | Null, Bulk string, or set of bulk-string members |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SPOP my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.sadd("set", "a", "b", "c");
const popped = await redis.spop("set");
console.log(popped); // "a"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.spop("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.spop("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sPop("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.spop("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SPop(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.spop("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: Option<String> = connection.spop("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SRANDMEMBER
Source: https://upstash.com/docs/redis/commands/set/srandmember

Use `SRANDMEMBER` to get random members of a set without removing them.

Without a count one member is returned, or null when the key does not exist. A positive count returns up to that many distinct members, capped at the size of the set, while a negative count returns exactly that many members chosen independently, so the same member can appear more than once.

This is the command for sampling, random selection, and inspecting an unfamiliar set. Use [`SPOP`](/docs/redis/commands/set/spop) when the chosen members must be consumed.

## Syntax

```redis
SRANDMEMBER <key> [<count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<count>` | No | No | Number of members to return; a negative count may repeat members. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, Bulk string, or array of bulk-string members |
| RESP3 | Null, Bulk string, or array of bulk-string members |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SRANDMEMBER my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.sadd("set", "a", "b", "c");
const member = await redis.srandmember("set");
console.log(member); // "a"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.srandmember("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.srandmember("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sRandMember("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.srandmember("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SRandMember(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.srandmember("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.srandmember("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SREM
Source: https://upstash.com/docs/redis/commands/set/srem

Use `SREM` to remove one or more members from a set.

The reply counts only the members that were actually present, so removing something that is already gone is not an error. The key is deleted when its last member is removed, which is why a set that has been fully drained no longer exists rather than existing as an empty set.

## Syntax

```redis
SREM <key> <member> [<member> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<member>` | Yes | Yes | Member name. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SREM my-key member
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.sadd("set", "a", "b", "c");
const removed = await redis.srem("set", "a", "b", "d");
console.log(removed); // 2
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.srem("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.srem("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sRem("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.srem("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SRem(context.Background(), "my-key", "member").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.srem("my-key", "member");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.srem("my-key", "member")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SSCAN
Source: https://upstash.com/docs/redis/commands/set/sscan

Use `SSCAN` to iterate the members of a set in batches instead of reading them all at once.

Each call takes a cursor and returns the next cursor together with a batch of members. Start at cursor `0` and keep calling with the cursor from the previous reply until the server returns `0`, which ends the iteration. `MATCH` filters members with a glob-style pattern and `COUNT` hints at how much work each call should do.

Filtering happens after a batch is read, so a call can come back empty while the cursor is still non-zero: only the cursor tells you the iteration is over. Members present for the whole iteration are returned at least once, while those added or removed while it runs may or may not appear, and a member can be returned more than once, so keep the processing idempotent.

## Syntax

```redis
SSCAN <key> <cursor> [MATCH <pattern>] [COUNT <count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<cursor>` | Yes | No | Cursor returned by the previous call; start at `0`. |
| `MATCH <pattern>` | No | No | Return only elements matching this glob-style pattern. |
| `COUNT <count>` | No | No | Hint for how much work each iteration should do. |

## Important points

* This operation can inspect a large part of the database. Prefer cursor-based scans where possible and avoid unbounded use on hot paths.
* The cursor is opaque. Start with `0` and continue until the server returns cursor `0`; a single iteration may return no elements.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Two-element array: cursor and array of bulk-string members |
| RESP3 | Two-element array: cursor and array of bulk-string members |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SSCAN my-key 0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.sadd("key", "a", "ab","b", "c");
const [newCursor, fields] = await redis.sscan("key", 0, { match: "a*"});
console.log(newCursor); // likely `0` since this is a very small set
console.log(fields); // ["a", "ab"]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.sscan("my-key", 0)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.sscan("my-key", "0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sScan("my-key", "0");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.sscan("my-key", 0)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, _, err := client.SScan(context.Background(), "my-key", 0, "*", 0).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sscan("my-key", "0");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let iter: redis::Iter<String> = connection.sscan("my-key")?;
    for member in iter {
        println!("{member}");
    }
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SUNION
Source: https://upstash.com/docs/redis/commands/set/sunion

Use `SUNION` to get every member that appears in at least one of the given sets.

Duplicates across the sources collapse, since the result is itself a set, and keys that do not exist count as empty sets. The union is computed on each call and nothing is stored; use [`SUNIONSTORE`](/docs/redis/commands/set/sunionstore) to keep the result. It answers "or" questions over sets, such as everyone reachable through any of several audiences.

## Syntax

```redis
SUNION <key> [<key> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | Yes | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string members |
| RESP3 | Set of bulk-string members |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SUNION set1 set2
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.sadd("set1", "a", "b", "c");
await redis.sadd("set2", "c", "d", "e");
const union =  await redis.sunion("set1", "set2");
console.log(union); // ["a", "b", "c", "d", "e"]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.sunion("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.sunion("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sUnion("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.sunion(["my-key"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SUnion(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sunion("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.sunion(&["my-key"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SUNIONSTORE
Source: https://upstash.com/docs/redis/commands/set/sunionstore

Use `SUNIONSTORE` to compute the union of sets and store it in another key.

The destination is overwritten if it exists and deleted when the union is empty, and the reply is the number of members stored. Materializing the union is the usual way to build a combined audience once and then intersect or difference it with other sets, instead of recomputing it on every query.

## Syntax

```redis
SUNIONSTORE <destination> <key> [<key> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<destination>` | Yes | No | Redis key used as destination. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SUNIONSTORE destination set1 set2
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.sadd("set1", "a", "b", "c");
await redis.sadd("set2", "c", "d", "e");
await redis.sunionstore("destination", "set1", "set2");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.sunionstore("destination-key", "my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.sunionstore("destination-key", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.sUnionStore("destination-key", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.sunionstore("destination-key", ["my-key"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SUnionStore(context.Background(), "destination-key", "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.sunionstore("destination-key", "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.sunionstore("destination-key", &["my-key"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# BZMPOP
Source: https://upstash.com/docs/redis/commands/sorted-set/bzmpop

Use `BZMPOP` to pop members from the first non-empty sorted set among several, blocking until one has members or the timeout expires.

It is the blocking form of [`ZMPOP`](/docs/redis/commands/sorted-set/zmpop): keys are examined in the order given, so earlier keys act as higher priority queues, `MIN` or `MAX` chooses whether the lowest or highest scoring members are taken, and `COUNT` sets how many. The reply names the key that was popped from along with the members and their scores.

The timeout is in seconds, may be fractional, and `0` waits indefinitely; when it expires the reply is null. This is what a worker uses to wait on several priority queues at once without polling.

## Syntax

```redis
BZMPOP <timeout> <numkeys> <key> [<key> ...] (MIN | MAX) [COUNT <count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<timeout>` | Yes | No | Seconds to block; `0` blocks indefinitely. |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |
| `(MIN \| MAX)` | Yes | No | Which members to pop: `MIN` (lowest scores first) or `MAX` (highest scores first). |
| `COUNT <count>` | No | No | Maximum number of members to pop. |

## Important points

* A blocking form holds the request until data arrives or its timeout expires. Set the client/network timeout longer than the command timeout.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, or two-element array: key and array of member/score pairs |
| RESP3 | Null, or two-element array: key and array of member/score pairs |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
BZMPOP 1.5 1 my-key MIN
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.bzmpop("1.5", "1", "my-key", "MIN");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.bzmPop(1.5, "my-key", "MIN");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.bzmpop(1.5, 1, ["my-key"], min=True)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    _, result, err := client.BZMPop(context.Background(), 1500*time.Millisecond, "min", 0, "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.bzmpop(1.5, redis.clients.jedis.args.SortedSetOption.MIN, "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.bzmpop_min(1.5, "my-key", 1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# BZPOPMAX
Source: https://upstash.com/docs/redis/commands/sorted-set/bzpopmax

Use `BZPOPMAX` to pop the highest-scoring member from the first non-empty sorted set, blocking until one has a member or the timeout expires.

It is the blocking form of [`ZPOPMAX`](/docs/redis/commands/sorted-set/zpopmax) and accepts several keys, which are checked in the order given. The reply names the key the member came from together with the member and its score.

The timeout is in seconds, may be fractional, and `0` waits indefinitely; when it expires the reply is null. With the score used as a priority, this turns a sorted set into a priority queue whose consumers wait for work instead of polling, and clients blocked on the same key are served in the order they started waiting.

## Syntax

```redis
BZPOPMAX <key> [<key> ...] <timeout>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | Yes | Redis key targeted by the command. |
| `<timeout>` | Yes | No | Seconds to block; `0` blocks indefinitely. |

## Important points

* A blocking form holds the request until data arrives or its timeout expires. Set the client/network timeout longer than the command timeout.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, or three-element key/member/score array |
| RESP3 | Null, or three-element key/member/score array |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
BZPOPMAX my-key 1.5
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.bzpopmax("my-key", "1.5");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.bzPopMax("my-key", 1.5);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.bzpopmax(["my-key"], timeout=1.5)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.BZPopMax(context.Background(), 1500*time.Millisecond, "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.bzpopmax(1.5, "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.bzpopmax("my-key", 1.5)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# BZPOPMIN
Source: https://upstash.com/docs/redis/commands/sorted-set/bzpopmin

Use `BZPOPMIN` to pop the lowest-scoring member from the first non-empty sorted set, blocking until one has a member or the timeout expires.

It is the blocking form of [`ZPOPMIN`](/docs/redis/commands/sorted-set/zpopmin) and accepts several keys, checked in the order given, with the reply naming the key the member came from along with the member and its score.

The timeout is in seconds, may be fractional, and `0` waits indefinitely; when it expires the reply is null. Since the lowest score is popped first, a set scored by timestamp or priority number becomes a queue that hands out the most urgent item to whichever worker is waiting.

## Syntax

```redis
BZPOPMIN <key> [<key> ...] <timeout>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | Yes | Redis key targeted by the command. |
| `<timeout>` | Yes | No | Seconds to block; `0` blocks indefinitely. |

## Important points

* A blocking form holds the request until data arrives or its timeout expires. Set the client/network timeout longer than the command timeout.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, or three-element key/member/score array |
| RESP3 | Null, or three-element key/member/score array |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
BZPOPMIN my-key 1.5
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.bzpopmin("my-key", "1.5");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.bzPopMin("my-key", 1.5);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.bzpopmin(["my-key"], timeout=1.5)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.BZPopMin(context.Background(), 1500*time.Millisecond, "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.bzpopmin(1.5, "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.bzpopmin("my-key", 1.5)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# Sorted Set commands
Source: https://upstash.com/docs/redis/commands/sorted-set/overview

<CardGroup cols={2}>
<Card title="BZMPOP" href="/redis/commands/sorted-set/bzmpop">Blocking pop from sorted sets</Card>
<Card title="BZPOPMAX" href="/redis/commands/sorted-set/bzpopmax">Blocking pop max score member</Card>
<Card title="BZPOPMIN" href="/redis/commands/sorted-set/bzpopmin">Blocking pop min score member</Card>
<Card title="ZADD" href="/redis/commands/sorted-set/zadd">Add members with scores</Card>
<Card title="ZCARD" href="/redis/commands/sorted-set/zcard">Get sorted set cardinality</Card>
<Card title="ZCOUNT" href="/redis/commands/sorted-set/zcount">Count members in score range</Card>
<Card title="ZDIFF" href="/redis/commands/sorted-set/zdiff">Get sorted set difference</Card>
<Card title="ZDIFFSTORE" href="/redis/commands/sorted-set/zdiffstore">Store sorted set difference</Card>
<Card title="ZINCRBY" href="/redis/commands/sorted-set/zincrby">Increment member's score</Card>
<Card title="ZINTER" href="/redis/commands/sorted-set/zinter">Get sorted set intersection</Card>
<Card title="ZINTERCARD" href="/redis/commands/sorted-set/zintercard">Get intersection cardinality</Card>
<Card title="ZINTERSTORE" href="/redis/commands/sorted-set/zinterstore">Store sorted set intersection</Card>
<Card title="ZLEXCOUNT" href="/redis/commands/sorted-set/zlexcount">Count members in lex range</Card>
<Card title="ZMPOP" href="/redis/commands/sorted-set/zmpop">Pop from the first non-empty sorted set</Card>
<Card title="ZMSCORE" href="/redis/commands/sorted-set/zmscore">Get scores of multiple members</Card>
<Card title="ZPOPMAX" href="/redis/commands/sorted-set/zpopmax">Pop members with highest scores</Card>
<Card title="ZPOPMIN" href="/redis/commands/sorted-set/zpopmin">Pop members with lowest scores</Card>
<Card title="ZRANDMEMBER" href="/redis/commands/sorted-set/zrandmember">Get random members</Card>
<Card title="ZRANGE" href="/redis/commands/sorted-set/zrange">Get members by index, score, or lex range</Card>
<Card title="ZRANGEBYLEX" href="/redis/commands/sorted-set/zrangebylex">Get members by lex range</Card>
<Card title="ZRANGEBYSCORE" href="/redis/commands/sorted-set/zrangebyscore">Get members by score range</Card>
<Card title="ZRANGESTORE" href="/redis/commands/sorted-set/zrangestore">Store the result of a range query</Card>
<Card title="ZRANK" href="/redis/commands/sorted-set/zrank">Get member's rank</Card>
<Card title="ZREM" href="/redis/commands/sorted-set/zrem">Remove members</Card>
<Card title="ZREMRANGEBYLEX" href="/redis/commands/sorted-set/zremrangebylex">Remove members by lex range</Card>
<Card title="ZREMRANGEBYRANK" href="/redis/commands/sorted-set/zremrangebyrank">Remove members by rank range</Card>
<Card title="ZREMRANGEBYSCORE" href="/redis/commands/sorted-set/zremrangebyscore">Remove members by score range</Card>
<Card title="ZREVRANGE" href="/redis/commands/sorted-set/zrevrange">Get members in reverse order</Card>
<Card title="ZREVRANGEBYLEX" href="/redis/commands/sorted-set/zrevrangebylex">Reverse lex range query</Card>
<Card title="ZREVRANGEBYSCORE" href="/redis/commands/sorted-set/zrevrangebyscore">Reverse score range query</Card>
<Card title="ZREVRANK" href="/redis/commands/sorted-set/zrevrank">Get member's reverse rank</Card>
<Card title="ZSCAN" href="/redis/commands/sorted-set/zscan">Incrementally iterate sorted set</Card>
<Card title="ZSCORE" href="/redis/commands/sorted-set/zscore">Get member's score</Card>
<Card title="ZUNION" href="/redis/commands/sorted-set/zunion">Get sorted set union</Card>
<Card title="ZUNIONSTORE" href="/redis/commands/sorted-set/zunionstore">Store sorted set union</Card>
</CardGroup>

# ZADD
Source: https://upstash.com/docs/redis/commands/sorted-set/zadd

Use `ZADD` to add members with a score to a sorted set, or to change the score of members that are already there.

Members are kept ordered by score, and members with equal scores are ordered lexicographically, which is what makes range queries by score or by member name possible. Scores are double precision floats and accept `+inf` and `-inf`. The key is created on first use, and the reply counts the members that were added.

`NX` only adds new members and never changes an existing score, while `XX` only updates members that already exist. `GT` and `LT` update a score only when the new one is greater or less than the current one, which is how you keep a running maximum or minimum, such as a high score or an earliest deadline, without reading the old value first. `CH` makes the reply count every member that changed, added or updated, instead of only the new ones. `INCR` treats the given score as an increment and returns the member's new score, behaving like [`ZINCRBY`](/docs/redis/commands/sorted-set/zincrby) for a single member and returning null when a condition prevented the change.

## Syntax

```redis
ZADD <key> [NX | XX] [GT | LT] [CH] [INCR] <score> <member> [<score> <member> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `(NX \| XX)` | No | No | Choose one form: `NX` (only add new members, never update an existing one); `XX` (only update members that already exist). |
| `(GT \| LT)` | No | No | Choose one form: `GT` (only update when the new score is greater than the current one); `LT` (only update when it is less). Neither blocks new members from being added. |
| `CH` | No | No | Count changed members rather than only new members. |
| `INCR` | No | No | Increment one member instead of adding normally. |
| `<score> <member>` | Yes | Yes | Score followed by the member it applies to. Repeat to add several members. |

## Important points

* `NX` cannot be combined with `XX`, `GT`, or `LT`, and `GT` and `LT` cannot be used together.
* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array or Integer or Bulk string containing a number |
| RESP3 | Null or Integer or Double |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZADD my-key 1.5 member
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zadd(
    "key",
    { score: 2, member: "member" },
    { score: 3, member: "member2"},
);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zadd("my-key", {"member": 1.5})
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zadd("my-key", "1.5", "member");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zAdd("my-key", { score: 1.5, value: "member" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zadd("my-key", {"member": 1.5})
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZAdd(context.Background(), "my-key", redis.Z{Score: 1.5, Member: "member"}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zadd("my-key", 1.5, "member");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zadd("my-key", "member", 1.5)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZCARD
Source: https://upstash.com/docs/redis/commands/sorted-set/zcard

Use `ZCARD` to get the number of members in a sorted set.

The reply is `0` when the key does not exist. The size is maintained by Redis, so the command is cheap whatever the size of the set. Use [`ZCOUNT`](/docs/redis/commands/sorted-set/zcount) when you need the number of members within a score range rather than the total.

## Syntax

```redis
ZCARD <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZCARD my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zadd("key",
    { score: 1, member: "one"},
    { score: 2, member: "two" },
);
const elements = await redis.zrank("key");
console.log(elements); // 2
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zcard("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zcard("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zCard("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zcard("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZCard(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zcard("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zcard("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZCOUNT
Source: https://upstash.com/docs/redis/commands/sorted-set/zcount

Use `ZCOUNT` to count the members of a sorted set whose score falls in a range.

Both bounds are inclusive by default; prefix a bound with `(` to make it exclusive, and use `-inf` and `+inf` for open ends, so `ZCOUNT key (5 +inf` counts everything scored above 5. Redis finds the range by rank rather than scanning the whole set, so the count is fast even on large sets.

With timestamps as scores this is how you count events in a time window without transferring them.

## Syntax

```redis
ZCOUNT <key> <min> <max>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<min>` | Yes | No | Minimum score: a number, `-inf`, or `(score` for an exclusive bound. |
| `<max>` | Yes | No | Maximum score: a number, `+inf`, or `(score` for an exclusive bound. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZCOUNT my-key 1.5 1.5
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zadd("key",
    { score: 1, member: "one"},
    { score: 2, member: "two" },
);
const elements = await redis.zcount("key", "(1", "+inf");
console.log(elements); // 1
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zcount("my-key", 1.5, 1.5)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zcount("my-key", "1.5", "1.5");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zCount("my-key", 1.5, 1.5);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zcount("my-key", 1.5, 1.5)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZCount(context.Background(), "my-key", "1.5", "1.5").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zcount("my-key", 1.5, 1.5);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zcount("my-key", 1.5, 1.5)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZDIFF
Source: https://upstash.com/docs/redis/commands/sorted-set/zdiff

Use `ZDIFF` to get the members of the first sorted set that do not appear in any of the others.

`<numkeys>` states how many keys follow. Scores come from the first set and are returned only when `WITHSCORES` is given, and the result keeps the ordering of that set. Keys that do not exist count as empty.

The result is computed on each call and not stored; use [`ZDIFFSTORE`](/docs/redis/commands/sorted-set/zdiffstore) to keep it. This is how you exclude one group from a ranking, for example dropping already-seen items from a scored feed.

## Syntax

```redis
ZDIFF <numkeys> <key> [<key> ...] [WITHSCORES]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |
| `WITHSCORES` | No | No | Include sorted-set scores. |

## Important points

* `numkeys` must equal the number of key arguments that immediately follow it.
* Pair-based results may be flattened into one alternating array in RESP2 while RESP3 preserves nested pairs or a map.
* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of members, or flat member/score array with `WITHSCORES` |
| RESP3 | Array of members, or array of member/score pairs with `WITHSCORES` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZDIFF 1 my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zdiff(["my-key"])
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zdiff("1", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zDiff("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zdiff(["my-key"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZDiff(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zdiff("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("ZDIFF");
    command.arg("1");
    command.arg("my-key");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZDIFFSTORE
Source: https://upstash.com/docs/redis/commands/sorted-set/zdiffstore

Use `ZDIFFSTORE` to compute the difference between sorted sets and store it in another key.

The destination receives the members of the first source that appear in none of the others, keeping their scores from that source. It is overwritten if it exists and deleted when the result is empty, and the reply is the number of members stored.

Materializing the result is worthwhile when the same filtered ranking is read repeatedly or paged through, since the destination is an ordinary sorted set.

## Syntax

```redis
ZDIFFSTORE <destination> <numkeys> <key> [<key> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<destination>` | Yes | No | Redis key used as destination. |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |

## Important points

* `numkeys` must equal the number of key arguments that immediately follow it.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZDIFFSTORE destination-key 1 my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const values = await redis.zdiffstore("destination", 2, "key1", "key2");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zdiffstore("destination-key", ["my-key"])
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zdiffstore("destination-key", "1", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zDiffStore("destination-key", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zdiffstore("destination-key", ["my-key"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZDiffStore(context.Background(), "destination-key", "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zdiffstore("destination-key", "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("ZDIFFSTORE");
    command.arg("destination-key");
    command.arg("1");
    command.arg("my-key");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZINCRBY
Source: https://upstash.com/docs/redis/commands/sorted-set/zincrby

Use `ZINCRBY` to add an increment to the score of a member of a sorted set.

A member that is not there yet is created with the increment as its score, and a missing key is created too, so no initialization step is needed. The increment may be negative to move a member down. The reply is the new score.

Because the update is atomic and the set stays ordered, this is the core operation behind leaderboards, vote counts, and any ranking that has to reflect concurrent updates: increment a member's score and read the position back with [`ZRANK`](/docs/redis/commands/sorted-set/zrank) or the top of the ranking with [`ZRANGE`](/docs/redis/commands/sorted-set/zrange).

## Syntax

```redis
ZINCRBY <key> <increment> <member>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<increment>` | Yes | No | Amount to add to the member's score. |
| `<member>` | Yes | No | Member name. |

## Important points

* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string containing a number |
| RESP3 | Double |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZINCRBY my-key 1 member
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zadd("key", 1, "member");
const value = await redis.zincrby("key", 2, "member");
console.log(value); // 3
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zincrby("my-key", 1, "member")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zincrby("my-key", "1", "member");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zIncrBy("my-key", 1, "member");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zincrby("my-key", 1, "member")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZIncrBy(context.Background(), "my-key", 1, "member").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zincrby("my-key", 1, "member");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zincr("my-key", "member", 1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZINTER
Source: https://upstash.com/docs/redis/commands/sorted-set/zinter

Use `ZINTER` to get the members that appear in every one of the given sorted sets.

`<numkeys>` states how many keys follow. By default the score of each member in the result is the sum of its scores across all the inputs, which is what makes intersections useful for combining rankings. `WEIGHTS` multiplies each input's scores by a factor before they are combined, so one source can count more than another, and `AGGREGATE` replaces the sum with `MIN` or `MAX`. `WITHSCORES` returns the computed scores along with the members.

The result is not stored; use [`ZINTERSTORE`](/docs/redis/commands/sorted-set/zinterstore) to keep it or [`ZINTERCARD`](/docs/redis/commands/sorted-set/zintercard) when only the size matters.

## Syntax

```redis
ZINTER <numkeys> <key> [<key> ...]
  [WEIGHTS <weight> [<weight> ...]]
  [AGGREGATE SUM | MIN | MAX]
  [WITHSCORES]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |
| `WEIGHTS <weight>` | No | Yes | Multiplier applied to each input key's scores, in the order the keys were given. |
| `(AGGREGATE SUM \| MIN \| MAX)` | No | No | Under `AGGREGATE`, choose one form: `SUM`; `MIN`; `MAX`. |
| `WITHSCORES` | No | No | Include sorted-set scores. |

## Important points

* `numkeys` must equal the number of key arguments that immediately follow it.
* Pair-based results may be flattened into one alternating array in RESP2 while RESP3 preserves nested pairs or a map.
* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of members, or flat member/score array with `WITHSCORES` |
| RESP3 | Array of members, or array of member/score pairs with `WITHSCORES` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZINTER 1 my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zinter(["my-key"])
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zinter("1", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zInter("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zinter(["my-key"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZInter(context.Background(), &redis.ZStore{Keys: []string{"my-key"}}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zinter(new redis.clients.jedis.params.ZParams(), "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("ZINTER");
    command.arg("1");
    command.arg("my-key");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZINTERCARD
Source: https://upstash.com/docs/redis/commands/sorted-set/zintercard

Use `ZINTERCARD` to get the number of members that the given sorted sets have in common, without building the intersection.

`<numkeys>` states how many keys follow. `LIMIT` stops the count once that many common members are found and returns the limit, which answers "do these sets overlap by at least N" far more cheaply than a full intersection; `LIMIT 0` means no limit. Only the count is computed and returned, so nothing is stored and no scores are transferred.

## Syntax

```redis
ZINTERCARD <numkeys> <key> [<key> ...] [LIMIT <limit>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |
| `LIMIT <limit>` | No | No | Stop counting once this many members match; `0` means no limit. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZINTERCARD 1 my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zintercard("1", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zInterCard("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zintercard(1, ["my-key"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZInterCard(context.Background(), 0, "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zintercard("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("ZINTERCARD");
    command.arg("1");
    command.arg("my-key");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZINTERSTORE
Source: https://upstash.com/docs/redis/commands/sorted-set/zinterstore

Use `ZINTERSTORE` to compute the intersection of sorted sets and store it in another key.

The destination gets the members present in every input, scored by the sum of their scores by default, or by `MIN` or `MAX` when `AGGREGATE` is given, with `WEIGHTS` scaling each input beforehand. The destination is overwritten if it exists and deleted when the intersection is empty, and the reply is the number of members stored.

This is how a combined ranking is built once, for instance blending a relevance score with a popularity score, and then paged through with [`ZRANGE`](/docs/redis/commands/sorted-set/zrange).

## Syntax

```redis
ZINTERSTORE <destination> <numkeys> <key> [<key> ...]
  [WEIGHTS <weight> [<weight> ...]]
  [AGGREGATE SUM | MIN | MAX]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<destination>` | Yes | No | Redis key used as destination. |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |
| `WEIGHTS <weight>` | No | Yes | Multiplier applied to each input key's scores, in the order the keys were given. |
| `(AGGREGATE SUM \| MIN \| MAX)` | No | No | Under `AGGREGATE`, choose one form: `SUM`; `MIN`; `MAX`. |

## Important points

* `numkeys` must equal the number of key arguments that immediately follow it.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZINTERSTORE destination-key 1 my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zadd(
    "key1",
    { score: 1, member: "member1" },
)
await redis.zadd(
    "key2",
    { score: 1, member: "member1" },
    { score: 2, member: "member2" },
)

const res = await redis.zinterstore("destination", 2, ["key1", "key2"]);
console.log(res) // 1
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zinterstore("destination-key", ["my-key"])
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zinterstore("destination-key", "1", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zInterStore("destination-key", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zinterstore("destination-key", ["my-key"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZInterStore(context.Background(), "destination-key", &redis.ZStore{Keys: []string{"my-key"}}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zinterstore("destination-key", "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zinterstore("destination-key", &["my-key"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZLEXCOUNT
Source: https://upstash.com/docs/redis/commands/sorted-set/zlexcount

Use `ZLEXCOUNT` to count the members of a sorted set that fall in a lexicographic range.

Bounds are written as `[member` for inclusive and `(member` for exclusive, with `-` and `+` standing for the lowest and highest possible members, so `ZLEXCOUNT key [a (c` counts members from `a` up to but not including `c`.

Lexicographic ordering only holds when every member of the set has the same score, which is the trick behind using a sorted set as a sorted index of strings; with mixed scores the result is not meaningful. Use [`ZRANGEBYLEX`](/docs/redis/commands/sorted-set/zrangebylex) or [`ZRANGE`](/docs/redis/commands/sorted-set/zrange) with `BYLEX` to retrieve the members themselves.

## Syntax

```redis
ZLEXCOUNT <key> <min> <max>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<min>` | Yes | No | Lexicographic minimum: `-`, `+`, `[member` (inclusive), or `(member` (exclusive). |
| `<max>` | Yes | No | Lexicographic maximum: `-`, `+`, `[member` (inclusive), or `(member` (exclusive). |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZLEXCOUNT my-key - +
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zadd("key",
    { score: 1, member: "one"},
    { score: 2, member: "two" },
);
const elements = await redis.zlexcount("key", "two", "+");
console.log(elements); // 1
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zlexcount("my-key", "-", "+")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zlexcount("my-key", "-", "+");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zLexCount("my-key", "-", "+");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zlexcount("my-key", "-", "+")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZLexCount(context.Background(), "my-key", "-", "+").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zlexcount("my-key", "-", "+");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zlexcount("my-key", "-", "+")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZMPOP
Source: https://upstash.com/docs/redis/commands/sorted-set/zmpop

Use `ZMPOP` to pop members from the first of several sorted sets that is not empty.

`<numkeys>` states how many keys follow, `MIN` or `MAX` chooses whether the lowest or the highest scoring members are taken, and `COUNT` sets how many to pop. Keys are examined in the order given and only the first non-empty one is touched, so listing a high priority queue first drains it before the others are considered.

The reply names the key that was popped from along with the members and their scores, which matters when a worker watches several queues. When every key is empty the reply is null; use [`BZMPOP`](/docs/redis/commands/sorted-set/bzmpop) to wait instead of returning.

## Syntax

```redis
ZMPOP <numkeys> <key> [<key> ...] (MIN | MAX) [COUNT <count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |
| `(MIN \| MAX)` | Yes | No | Which members to pop: `MIN` (lowest scores first) or `MAX` (highest scores first). |
| `COUNT <count>` | No | No | Maximum number of members to pop. |

## Important points

* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, or two-element array: key and array of member/score pairs |
| RESP3 | Null, or two-element array: key and array of member/score pairs |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZMPOP 1 my-key MIN
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zmpop("1", "my-key", "MIN");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zmPop("my-key", "MIN");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zmpop(1, ["my-key"], min=True)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    _, result, err := client.ZMPop(context.Background(), "min", 0, "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zmpop(redis.clients.jedis.args.SortedSetOption.MIN, "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zmpop_min("my-key", 1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZMSCORE
Source: https://upstash.com/docs/redis/commands/sorted-set/zmscore

Use `ZMSCORE` to get the scores of several members of a sorted set in one call.

The reply holds one score per member, in the order requested, with null for members that are not in the set, so it lines up with your input list. A missing key yields all nulls rather than an error. It replaces a series of [`ZSCORE`](/docs/redis/commands/sorted-set/zscore) round trips when you need to read a batch of scores, such as the current rating of every item on a page.

## Syntax

```redis
ZMSCORE <key> <member> [<member> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<member>` | Yes | Yes | Member name. |

## Important points

* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string scores or null values, one per member |
| RESP3 | Array of double scores or null values, one per member |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZMSCORE my-key member
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zadd("key",
    { score: 1, member: "m1" },
    { score: 2, member: "m2" },
    { score: 3, member: "m3" },
    { score: 4, member: "m4" },
)

const scores = await redis.zmscore("key", ["m2", "m4"])
console.log(scores) // [2, 4]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zmscore("my-key", ["member"])
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zmscore("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zmScore("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zmscore("my-key", ["member"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZMScore(context.Background(), "my-key", "member").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zmscore("my-key", "member");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zscore_multiple("my-key", &["member"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZPOPMAX
Source: https://upstash.com/docs/redis/commands/sorted-set/zpopmax

Use `ZPOPMAX` to remove and return the members with the highest scores in a sorted set.

Without a count one member is returned with its score, and with a count up to that many are removed, ordered from the highest score down. A missing or empty key returns an empty result, and the key is deleted once the last member is popped.

Since the read and the removal are atomic, no two workers can take the same member, which turns a sorted set into a priority queue where the score is the priority. [`BZPOPMAX`](/docs/redis/commands/sorted-set/bzpopmax) is the blocking form.

## Syntax

```redis
ZPOPMAX <key> [<count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<count>` | No | No | Number of members to pop. |

## Important points

* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Flat array of alternating members and bulk-string scores |
| RESP3 | Flat array of alternating members and double scores |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZPOPMAX my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const popped = await redis.zpopmax("key", 4);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zpopmax("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zpopmax("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zPopMax("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zpopmax("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZPopMax(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zpopmax("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zpopmax("my-key", 1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZPOPMIN
Source: https://upstash.com/docs/redis/commands/sorted-set/zpopmin

Use `ZPOPMIN` to remove and return the members with the lowest scores in a sorted set.

Without a count one member is returned with its score, and with a count up to that many are removed, ordered from the lowest score up. The key is deleted once the last member is popped.

With a timestamp or a priority number as the score, this is how a sorted set becomes a scheduler or a priority queue: the next item due is always the minimum, and popping it atomically means only one worker can pick it up. [`BZPOPMIN`](/docs/redis/commands/sorted-set/bzpopmin) is the blocking form.

## Syntax

```redis
ZPOPMIN <key> [<count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<count>` | No | No | Number of members to pop. |

## Important points

* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Flat array of alternating members and bulk-string scores |
| RESP3 | Flat array of alternating members and double scores |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZPOPMIN my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const popped = await redis.zpopmin("key", 4);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zpopmin("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zpopmin("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zPopMin("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zpopmin("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZPopMin(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zpopmin("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zpopmin("my-key", 1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZRANDMEMBER
Source: https://upstash.com/docs/redis/commands/sorted-set/zrandmember

Use `ZRANDMEMBER` to get random members of a sorted set without removing them.

Without a count one member is returned, or null when the key does not exist. A positive count returns up to that many distinct members, capped at the size of the set, while a negative count returns exactly that many members chosen independently, so repeats are possible. `WITHSCORES` returns each member together with its score.

Use it for sampling and random selection, and [`ZPOPMIN`](/docs/redis/commands/sorted-set/zpopmin) or [`ZPOPMAX`](/docs/redis/commands/sorted-set/zpopmax) when members should be consumed.

## Syntax

```redis
ZRANDMEMBER <key> [<count> [WITHSCORES]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<count> [WITHSCORES]` | No | No | Number of members to return; a negative count may repeat members. `WITHSCORES` also returns each member's score. |

## Important points

* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, Bulk string, array of members, or flat member/score array |
| RESP3 | Null, Bulk string, array of members, or array of member/score pairs |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZRANDMEMBER my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zrandmember("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zrandmember("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zRandMember("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zrandmember("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZRandMember(context.Background(), "my-key", 1).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zrandmember("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result: Option<String> = connection.zrandmember("my-key", None)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZRANGE
Source: https://upstash.com/docs/redis/commands/sorted-set/zrange

Use `ZRANGE` to read a range of members from a sorted set.

By default `<start>` and `<stop>` are zero-based indexes into the ordering, inclusive, and may be negative to count from the end, so `0 -1` returns everything and `0 9` the ten lowest scoring members.

The option that follows changes what those two arguments mean. `BYSCORE` reads them as score bounds, inclusive unless prefixed with `(` and accepting `-inf` and `+inf`. `BYLEX` reads them as member bounds written `[member`, `(member`, `-`, or `+`, which is only meaningful when all members share the same score. `REV` reverses the order, and with `BYSCORE` or `BYLEX` it also means the bounds are given from the higher one to the lower one. `LIMIT <offset> <count>` pages through the result and requires `BYSCORE` or `BYLEX`. `WITHSCORES` includes each member's score.

`ZRANGE` covers everything `ZRANGEBYSCORE`, `ZRANGEBYLEX`, `ZREVRANGE`, and their variants do, and it is the command Redis recommends for new code. Use [`ZRANGESTORE`](/docs/redis/commands/sorted-set/zrangestore) to store the result instead of returning it.

## Syntax

```redis
ZRANGE <key> <start> <stop>
  [BYSCORE | BYLEX]
  [REV]
  [LIMIT <offset> <count>]
  [WITHSCORES]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<start>` | Yes | No | Lower bound. Interpreted as a rank by default, as a score with `BYSCORE`, or lexicographically with `BYLEX`. |
| `<stop>` | Yes | No | Upper bound. Interpreted as a rank by default, as a score with `BYSCORE`, or lexicographically with `BYLEX`. |
| `(BYSCORE \| BYLEX)` | No | No | How the two range arguments are read: `BYSCORE` as score bounds (inclusive, or exclusive with a `(` prefix, and `-inf`/`+inf` allowed), `BYLEX` as member bounds (`[member`, `(member`, `-`, or `+`), which only makes sense when every member has the same score. They are zero-based indexes when neither is given. |
| `REV` | No | No | Reverse the result order. |
| `LIMIT <offset> <count>` | No | No | Skip `<offset>` matches and return at most `<count>`; a `<count>` of `-1` returns all remaining. |
| `WITHSCORES` | No | No | Include sorted-set scores. |

## Important points

* Pair-based results may be flattened into one alternating array in RESP2 while RESP3 preserves nested pairs or a map.
* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of members, or flat member/score array with `WITHSCORES` |
| RESP3 | Array of members, or array of member/score pairs with `WITHSCORES` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZRANGE my-key 0 -1
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zadd("key",
    { score: 1, member: "m1" },
    { score: 2, member: "m2" },
)
const res = await redis.zrange("key", 1, 3)
console.log(res) // ["m2"]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zrange("my-key", 0, -1)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zrange("my-key", "0", "-1");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zRange("my-key", 0, -1);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zrange("my-key", 0, -1)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZRange(context.Background(), "my-key", 0, -1).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zrange("my-key", 0, -1);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zrange("my-key", 0, -1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZRANGEBYLEX
Source: https://upstash.com/docs/redis/commands/sorted-set/zrangebylex

<Warning>
  Prefer [`ZRANGE`](/docs/redis/commands/sorted-set/zrange) with `BYLEX` in new code: `ZRANGE <key> <min> <max> BYLEX [LIMIT <offset> <count>]`.
</Warning>

Use `ZRANGEBYLEX` to list the members of a sorted set that fall in a lexicographic range.

Bounds are written `[member` for inclusive and `(member` for exclusive, with `-` and `+` standing for the lowest and highest possible members. `LIMIT <offset> <count>` pages through the result.

The ordering used here is the byte order of member names, which is only meaningful when every member has the same score. Giving all members score `0` turns a sorted set into a sorted string index, which is how prefix lookups and autocomplete are built on top of it.

## Syntax

```redis
ZRANGEBYLEX <key> <min> <max> [LIMIT <offset> <count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<min>` | Yes | No | Lexicographic minimum: `-`, `+`, `[member` (inclusive), or `(member` (exclusive). |
| `<max>` | Yes | No | Lexicographic maximum: `-`, `+`, `[member` (inclusive), or `(member` (exclusive). |
| `LIMIT <offset> <count>` | No | No | Skip `<offset>` matches and return at most `<count>`; a `<count>` of `-1` returns all remaining. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string members |
| RESP3 | Array of bulk-string members |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZRANGEBYLEX my-key - +
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zrangebylex("my-key", "-", "+")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zrangebylex("my-key", "-", "+");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zRangeByLex("my-key", "-", "+");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zrangebylex("my-key", "-", "+")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZRangeByLex(context.Background(), "my-key", &redis.ZRangeBy{Min: "-", Max: "+"}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zrangeByLex("my-key", "-", "+");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zrangebylex("my-key", "-", "+")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZRANGEBYSCORE
Source: https://upstash.com/docs/redis/commands/sorted-set/zrangebyscore

<Warning>
  Prefer [`ZRANGE`](/docs/redis/commands/sorted-set/zrange) with `BYSCORE` in new code: `ZRANGE <key> <min> <max> BYSCORE [LIMIT <offset> <count>] [WITHSCORES]`.
</Warning>

Use `ZRANGEBYSCORE` to list the members of a sorted set whose score falls in a range, from the lowest score up.

Bounds are inclusive by default, become exclusive when prefixed with `(`, and accept `-inf` and `+inf` for open ends. `WITHSCORES` returns each member with its score, and `LIMIT <offset> <count>` pages through the result, which is how you take the first N above a threshold without transferring the rest.

With timestamps as scores this is the natural range query over time.

## Syntax

```redis
ZRANGEBYSCORE <key> <min> <max> [WITHSCORES] [LIMIT <offset> <count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<min>` | Yes | No | Minimum score: a number, `-inf`, or `(score` for an exclusive bound. |
| `<max>` | Yes | No | Maximum score: a number, `+inf`, or `(score` for an exclusive bound. |
| `WITHSCORES` | No | No | Include sorted-set scores. |
| `LIMIT <offset> <count>` | No | No | Skip `<offset>` matches and return at most `<count>`; a `<count>` of `-1` returns all remaining. |

## Important points

* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of members, or flat member/score array with `WITHSCORES` |
| RESP3 | Array of members, or array of member/score pairs with `WITHSCORES` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZRANGEBYSCORE my-key 1.5 1.5
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zrangebyscore("my-key", "1.5", "1.5")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zrangebyscore("my-key", "1.5", "1.5");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zRangeByScore("my-key", 1.5, 1.5);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zrangebyscore("my-key", "1.5", "1.5")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZRangeByScore(context.Background(), "my-key", &redis.ZRangeBy{Min: "1.5", Max: "1.5"}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zrangeByScore("my-key", 1.5, 1.5);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zrangebyscore("my-key", 1.5, 1.5)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZRANGESTORE
Source: https://upstash.com/docs/redis/commands/sorted-set/zrangestore

Use `ZRANGESTORE` to run the same selection as [`ZRANGE`](/docs/redis/commands/sorted-set/zrange) and store the result in another sorted set instead of returning it.

The selection works exactly as it does for `ZRANGE`: by index, or by score with `BYSCORE`, or by member with `BYLEX`, with `REV` and `LIMIT` available in the same way. The members keep their original scores in the destination, which is overwritten if it exists and deleted when the range is empty. The reply is the number of members stored.

This is how a slice of a large ranking is materialized once, for instance the top thousand of a leaderboard, so that further intersections or repeated paging run against a small key.

## Syntax

```redis
ZRANGESTORE <dst> <src> <min> <max>
  [BYSCORE | BYLEX]
  [REV]
  [LIMIT <offset> <count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<dst>` | Yes | No | Redis key used as dst. |
| `<src>` | Yes | No | Redis key used as src. |
| `<min>` | Yes | No | Lower bound. Interpreted as a rank by default, as a score with `BYSCORE`, or lexicographically with `BYLEX`. |
| `<max>` | Yes | No | Upper bound. Interpreted as a rank by default, as a score with `BYSCORE`, or lexicographically with `BYLEX`. |
| `(BYSCORE \| BYLEX)` | No | No | How the two range arguments are read: `BYSCORE` as score bounds (inclusive, or exclusive with a `(` prefix, and `-inf`/`+inf` allowed), `BYLEX` as member bounds (`[member`, `(member`, `-`, or `+`), which only makes sense when every member has the same score. They are zero-based indexes when neither is given. |
| `REV` | No | No | Reverse the result order. |
| `LIMIT <offset> <count>` | No | No | Skip `<offset>` matches and return at most `<count>`; a `<count>` of `-1` returns all remaining. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZRANGESTORE destination-key source-key -inf +inf BYSCORE
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zrangestore("destination-key", "source-key", "-inf", "+inf", sortby="BYSCORE")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zrangestore("my-key", "my-key", "-inf", "+inf");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zRangeStore("my-key", "my-key", "-inf", "+inf");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zrangestore("destination-key", "source-key", "-inf", "+inf", byscore=True)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZRangeStore(context.Background(), "destination-key", redis.ZRangeArgs{Key: "source-key", Start: "-inf", Stop: "+inf", ByScore: true}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zrangestore("destination-key", "source-key", redis.clients.jedis.params.ZRangeParams.zrangeByScoreParams(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("ZRANGESTORE");
    command.arg("my-key");
    command.arg("my-key");
    command.arg("-inf");
    command.arg("+inf");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZRANK
Source: https://upstash.com/docs/redis/commands/sorted-set/zrank

Use `ZRANK` to get the position of a member in a sorted set, counting from the lowest score.

The rank is zero-based, so the lowest scoring member has rank `0`, and members with equal scores are ordered lexicographically. `WITHSCORE` returns the score alongside the rank. The reply is null when the member or the key does not exist.

This is how a leaderboard reports "you are number N": increment scores with [`ZINCRBY`](/docs/redis/commands/sorted-set/zincrby) and read the position back here, using [`ZREVRANK`](/docs/redis/commands/sorted-set/zrevrank) when rank `0` should be the highest score instead.

## Syntax

```redis
ZRANK <key> <member> [WITHSCORE]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<member>` | Yes | No | Member name. |
| `WITHSCORE` | No | No | Also return the member's score alongside its rank. |

## Important points

* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, Integer, or two-element rank/score array |
| RESP3 | Null, Integer, or two-element rank/score array |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZRANK my-key member
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const rank = await redis.zrank("key", "member");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zrank("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zrank("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zRank("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zrank("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZRank(context.Background(), "my-key", "member").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zrank("my-key", "member");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zrank("my-key", "member")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZREM
Source: https://upstash.com/docs/redis/commands/sorted-set/zrem

Use `ZREM` to remove one or more members from a sorted set.

The reply counts only the members that were actually present, so removing something already gone is not an error. The key is deleted when its last member is removed. To remove members by score, rank, or member range instead of by name, use [`ZREMRANGEBYSCORE`](/docs/redis/commands/sorted-set/zremrangebyscore), [`ZREMRANGEBYRANK`](/docs/redis/commands/sorted-set/zremrangebyrank), or [`ZREMRANGEBYLEX`](/docs/redis/commands/sorted-set/zremrangebylex).

## Syntax

```redis
ZREM <key> <member> [<member> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<member>` | Yes | Yes | Member name. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZREM my-key member
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zrem("key", "member");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zrem("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zrem("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zRem("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zrem("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZRem(context.Background(), "my-key", "member").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zrem("my-key", "member");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zrem("my-key", "member")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZREMRANGEBYLEX
Source: https://upstash.com/docs/redis/commands/sorted-set/zremrangebylex

Use `ZREMRANGEBYLEX` to remove the members of a sorted set that fall in a lexicographic range.

Bounds are written `[member` for inclusive and `(member` for exclusive, with `-` and `+` for the extremes. As with every lexicographic operation, the result is only meaningful when all members share the same score. The reply is the number of members removed, and the key is deleted when it becomes empty.

It is the bulk delete that matches [`ZRANGEBYLEX`](/docs/redis/commands/sorted-set/zrangebylex), useful for dropping a whole prefix from a sorted string index.

## Syntax

```redis
ZREMRANGEBYLEX <key> <min> <max>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<min>` | Yes | No | Lexicographic minimum: `-`, `+`, `[member` (inclusive), or `(member` (exclusive). |
| `<max>` | Yes | No | Lexicographic maximum: `-`, `+`, `[member` (inclusive), or `(member` (exclusive). |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZREMRANGEBYLEX my-key - +
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zremrangebylex("key", "alpha", "omega")
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zremrangebylex("my-key", "-", "+")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zremrangebylex("my-key", "-", "+");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zRemRangeByLex("my-key", "-", "+");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zremrangebylex("my-key", "-", "+")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZRemRangeByLex(context.Background(), "my-key", "-", "+").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zremrangeByLex("my-key", "-", "+");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zrembylex("my-key", "-", "+")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZREMRANGEBYRANK
Source: https://upstash.com/docs/redis/commands/sorted-set/zremrangebyrank

Use `ZREMRANGEBYRANK` to remove the members of a sorted set that occupy a range of positions.

Ranks are zero-based from the lowest score and may be negative to count from the top, so `ZREMRANGEBYRANK key 0 -101` keeps only the hundred highest scoring members. The reply is the number of members removed, and the key is deleted when it becomes empty.

This is the standard way to cap a leaderboard or any ranking to a fixed size after inserting into it, in the same spirit as [`LTRIM`](/docs/redis/commands/list/ltrim) for lists.

## Syntax

```redis
ZREMRANGEBYRANK <key> <start> <stop>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<start>` | Yes | No | Start index; negative values count from the end. |
| `<stop>` | Yes | No | Stop index, inclusive; negative values count from the end. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZREMRANGEBYRANK my-key 0 0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zremrangebyrank("key", 4, 20)
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zremrangebyrank("my-key", 0, 0)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zremrangebyrank("my-key", "0", "0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zRemRangeByRank("my-key", 0, 0);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zremrangebyrank("my-key", 0, 0)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZRemRangeByRank(context.Background(), "my-key", 0, 0).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zremrangeByRank("my-key", 0, 0);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zremrangebyrank("my-key", 0, 0)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZREMRANGEBYSCORE
Source: https://upstash.com/docs/redis/commands/sorted-set/zremrangebyscore

Use `ZREMRANGEBYSCORE` to remove the members of a sorted set whose score falls in a range.

Bounds are inclusive by default, become exclusive when prefixed with `(`, and accept `-inf` and `+inf`. The reply is the number of members removed, and the key is deleted when it becomes empty.

With timestamps as scores this is how a sliding window is maintained: drop everything older than the cutoff in one call, which is exactly what a sorted-set rate limiter does before counting what is left.

## Syntax

```redis
ZREMRANGEBYSCORE <key> <min> <max>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<min>` | Yes | No | Minimum score: a number, `-inf`, or `(score` for an exclusive bound. |
| `<max>` | Yes | No | Maximum score: a number, `+inf`, or `(score` for an exclusive bound. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZREMRANGEBYSCORE my-key 1.5 1.5
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zremrangebyscore("key", 2, 5)
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zremrangebyscore("my-key", 1.5, 1.5)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zremrangebyscore("my-key", "1.5", "1.5");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zRemRangeByScore("my-key", 1.5, 1.5);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zremrangebyscore("my-key", 1.5, 1.5)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZRemRangeByScore(context.Background(), "my-key", "1.5", "1.5").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zremrangeByScore("my-key", 1.5, 1.5);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zrembyscore("my-key", 1.5, 1.5)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZREVRANGE
Source: https://upstash.com/docs/redis/commands/sorted-set/zrevrange

<Warning>
  Prefer [`ZRANGE`](/docs/redis/commands/sorted-set/zrange) with `REV` in new code: `ZRANGE <key> <start> <stop> REV [WITHSCORES]`.
</Warning>

Use `ZREVRANGE` to list a range of members of a sorted set ordered from the highest score down.

`<start>` and `<stop>` are zero-based, inclusive positions in that reversed ordering, so `0 9` returns the ten highest scoring members, the usual shape of a leaderboard page. Negative indexes count from the end, and `WITHSCORES` includes each member's score.

## Syntax

```redis
ZREVRANGE <key> <start> <stop> [WITHSCORES]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<start>` | Yes | No | Start index; negative values count from the end. |
| `<stop>` | Yes | No | Stop index, inclusive; negative values count from the end. |
| `WITHSCORES` | No | No | Include sorted-set scores. |

## Important points

* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of members, or flat member/score array with `WITHSCORES` |
| RESP3 | Array of members, or array of member/score pairs with `WITHSCORES` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZREVRANGE my-key 0 0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zrevrange("my-key", 0, 0)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zrevrange("my-key", "0", "0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zRange("my-key", 0, 0, { REV: true });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zrevrange("my-key", 0, 0)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZRevRange(context.Background(), "my-key", 0, -1).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zrevrange("my-key", 0, -1);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zrevrange("my-key", 0, -1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZREVRANGEBYLEX
Source: https://upstash.com/docs/redis/commands/sorted-set/zrevrangebylex

<Warning>
  Prefer [`ZRANGE`](/docs/redis/commands/sorted-set/zrange) with `BYLEX` and `REV` in new code: `ZRANGE <key> <max> <min> BYLEX REV [LIMIT <offset> <count>]`.
</Warning>

Use `ZREVRANGEBYLEX` to list the members in a lexicographic range, in reverse order.

The bounds are given highest first, `<max>` before `<min>`, written `[member` for inclusive and `(member` for exclusive, with `+` and `-` for the extremes. `LIMIT <offset> <count>` pages through the result.

As with every lexicographic operation, the ordering is only meaningful when all members share the same score.

## Syntax

```redis
ZREVRANGEBYLEX <key> <max> <min> [LIMIT <offset> <count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<max>` | Yes | No | Lexicographic maximum: `-`, `+`, `[member` (inclusive), or `(member` (exclusive). |
| `<min>` | Yes | No | Lexicographic minimum: `-`, `+`, `[member` (inclusive), or `(member` (exclusive). |
| `LIMIT <offset> <count>` | No | No | Skip `<offset>` matches and return at most `<count>`; a `<count>` of `-1` returns all remaining. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string members |
| RESP3 | Array of bulk-string members |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZREVRANGEBYLEX my-key + -
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zrevrangebylex("my-key", "+", "-")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zrevrangebylex("my-key", "+", "-");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zRange("my-key", "+", "-", { BY: "LEX", REV: true });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zrevrangebylex("my-key", "+", "-")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZRevRangeByLex(context.Background(), "my-key", &redis.ZRangeBy{Min: "-", Max: "+"}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zrevrangeByLex("my-key", "+", "-");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zrevrangebylex("my-key", "+", "-")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZREVRANGEBYSCORE
Source: https://upstash.com/docs/redis/commands/sorted-set/zrevrangebyscore

<Warning>
  Prefer [`ZRANGE`](/docs/redis/commands/sorted-set/zrange) with `BYSCORE` and `REV` in new code: `ZRANGE <key> <max> <min> BYSCORE REV [LIMIT <offset> <count>] [WITHSCORES]`.
</Warning>

Use `ZREVRANGEBYSCORE` to list the members in a score range, ordered from the highest score down.

The bounds are given highest first, `<max>` before `<min>`. They are inclusive by default, become exclusive when prefixed with `(`, and accept `+inf` and `-inf`. `WITHSCORES` returns the scores and `LIMIT <offset> <count>` pages through the result, which is the usual way to fetch "the top N above a threshold".

## Syntax

```redis
ZREVRANGEBYSCORE <key> <max> <min> [WITHSCORES] [LIMIT <offset> <count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<max>` | Yes | No | Maximum score: a number, `+inf`, or `(score` for an exclusive bound. |
| `<min>` | Yes | No | Minimum score: a number, `-inf`, or `(score` for an exclusive bound. |
| `WITHSCORES` | No | No | Include sorted-set scores. |
| `LIMIT <offset> <count>` | No | No | Skip `<offset>` matches and return at most `<count>`; a `<count>` of `-1` returns all remaining. |

## Important points

* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of members, or flat member/score array with `WITHSCORES` |
| RESP3 | Array of members, or array of member/score pairs with `WITHSCORES` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZREVRANGEBYSCORE my-key 1.5 1.5
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zrevrangebyscore("my-key", "1.5", "1.5")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zrevrangebyscore("my-key", "1.5", "1.5");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zRange("my-key", 1.5, 1.5, { BY: "SCORE", REV: true });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zrevrangebyscore("my-key", "1.5", "1.5")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZRevRangeByScore(context.Background(), "my-key", &redis.ZRangeBy{Min: "1.5", Max: "1.5"}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zrevrangeByScore("my-key", 1.5, 1.5);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zrevrangebyscore("my-key", 1.5, 1.5)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZREVRANK
Source: https://upstash.com/docs/redis/commands/sorted-set/zrevrank

Use `ZREVRANK` to get the position of a member in a sorted set, counting from the highest score.

The rank is zero-based, so the top scoring member has rank `0`, which is the number a leaderboard usually wants to display. `WITHSCORE` returns the score alongside the rank, and the reply is null when the member or the key does not exist. Use [`ZRANK`](/docs/redis/commands/sorted-set/zrank) to count from the lowest score instead.

## Syntax

```redis
ZREVRANK <key> <member> [WITHSCORE]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<member>` | Yes | No | Member name. |
| `WITHSCORE` | No | No | Also return the member's score alongside its rank. |

## Important points

* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array, Integer, or two-element rank/score array |
| RESP3 | Null, Integer, or two-element rank/score array |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZREVRANK my-key member
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const rank = await redis.zrevrank("key", "member");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zrevrank("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zrevrank("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zRevRank("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zrevrank("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZRevRank(context.Background(), "my-key", "member").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zrevrank("my-key", "member");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zrevrank("my-key", "member")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZSCAN
Source: https://upstash.com/docs/redis/commands/sorted-set/zscan

Use `ZSCAN` to iterate the members of a sorted set in batches instead of reading them all at once.

Each call takes a cursor and returns the next cursor together with a batch of members and their scores. Start at cursor `0` and keep calling with the cursor from the previous reply until the server returns `0`. `MATCH` filters member names with a glob-style pattern and `COUNT` hints at how much work each call should do.

Filtering happens after a batch is read, so a call can come back empty while the cursor is still non-zero. Members present for the whole iteration are returned at least once, and members added or removed while it runs may or may not appear. When you want members in score order rather than in scan order, use [`ZRANGE`](/docs/redis/commands/sorted-set/zrange).

## Syntax

```redis
ZSCAN <key> <cursor> [MATCH <pattern>] [COUNT <count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<cursor>` | Yes | No | Cursor returned by the previous call; start at `0`. |
| `MATCH <pattern>` | No | No | Return only elements matching this glob-style pattern. |
| `COUNT <count>` | No | No | Hint for how much work each iteration should do. |

## Important points

* This operation can inspect a large part of the database. Prefer cursor-based scans where possible and avoid unbounded use on hot paths.
* The cursor is opaque. Start with `0` and continue until the server returns cursor `0`; a single iteration may return no elements.
* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Two-element array: cursor and flat member/score array |
| RESP3 | Two-element array: cursor and flat member/score array |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZSCAN my-key 0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zadd("key",
    { score: 1, member: "a" },
    { score: 2, member: "ab" },
    { score: 3, member: "b" },
    { score: 4, member: "c" },
    { score: 5, member: "d" },
)
const [newCursor, members] = await redis.zscan("key", 0, { match: "a*"});
console.log(newCursor); // likely `0` since this is a very small set
console.log(members); // ["a", "ab"]
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zscan("my-key", 0)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zscan("my-key", "0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zScan("my-key", "0");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zscan("my-key", 0)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, _, err := client.ZScan(context.Background(), "my-key", 0, "*", 0).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zscan("my-key", "0");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let iter: redis::Iter<(String, f64)> = connection.zscan("my-key")?;
    for (member, score) in iter {
        println!("{member}: {score}");
    }
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZSCORE
Source: https://upstash.com/docs/redis/commands/sorted-set/zscore

Use `ZSCORE` to get the score of a member of a sorted set.

The reply is null when the member or the key does not exist, so it doubles as a membership test that also tells you the value. Use [`ZMSCORE`](/docs/redis/commands/sorted-set/zmscore) to read several scores in one call, and [`ZRANK`](/docs/redis/commands/sorted-set/zrank) when you want the member's position rather than its score.

## Syntax

```redis
ZSCORE <key> <member>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<member>` | Yes | No | Member name. |

## Important points

* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string containing a number or Null bulk string or null array |
| RESP3 | Double or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZSCORE my-key member
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zadd("key",
    { score: 1, member: "m1" },
    { score: 2, member: "m2" },
    { score: 3, member: "m3" },
    { score: 4, member: "m4" },
)

const score = await redis.zscore("key", "m2")
console.log(score) // 2
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zscore("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zscore("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zScore("my-key", "member");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zscore("my-key", "member")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZScore(context.Background(), "my-key", "member").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zscore("my-key", "member");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zscore("my-key", "member")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZUNION
Source: https://upstash.com/docs/redis/commands/sorted-set/zunion

Use `ZUNION` to get every member that appears in at least one of the given sorted sets.

`<numkeys>` states how many keys follow. A member present in several inputs gets the sum of its scores by default, which is how scores from different sources are combined; `WEIGHTS` scales each input before that, and `AGGREGATE` replaces the sum with `MIN` or `MAX`. `WITHSCORES` returns the computed scores.

The result is computed on each call and not stored; use [`ZUNIONSTORE`](/docs/redis/commands/sorted-set/zunionstore) to keep it.

## Syntax

```redis
ZUNION <numkeys> <key> [<key> ...]
  [WEIGHTS <weight> [<weight> ...]]
  [AGGREGATE SUM | MIN | MAX]
  [WITHSCORES]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |
| `WEIGHTS <weight>` | No | Yes | Multiplier applied to each input key's scores, in the order the keys were given. |
| `(AGGREGATE SUM \| MIN \| MAX)` | No | No | Under `AGGREGATE`, choose one form: `SUM`; `MIN`; `MAX`. |
| `WITHSCORES` | No | No | Include sorted-set scores. |

## Important points

* `numkeys` must equal the number of key arguments that immediately follow it.
* RESP2 represents floating-point reply values as bulk strings; RESP3 may use native double replies. Client libraries commonly decode either form to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of members, or flat member/score array with `WITHSCORES` |
| RESP3 | Array of members, or array of member/score pairs with `WITHSCORES` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZUNION 1 my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.zunion(1, "my-key");
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zunion(["my-key"])
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zunion("1", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zUnion("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zunion(["my-key"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZUnion(context.Background(), redis.ZStore{Keys: []string{"my-key"}}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zunion(new redis.clients.jedis.params.ZParams(), "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("ZUNION");
    command.arg("1");
    command.arg("my-key");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# ZUNIONSTORE
Source: https://upstash.com/docs/redis/commands/sorted-set/zunionstore

Use `ZUNIONSTORE` to compute the union of sorted sets and store it in another key.

Members present in several inputs are scored by the sum of their scores by default, or by `MIN` or `MAX` when `AGGREGATE` is given, with `WEIGHTS` scaling each input first. The destination is overwritten if it exists and deleted when the union is empty, and the reply is the number of members stored.

Merging per-day or per-source rankings into a combined one, weighting them as you go, is the typical use: the destination is an ordinary sorted set that can then be paged with [`ZRANGE`](/docs/redis/commands/sorted-set/zrange).

## Syntax

```redis
ZUNIONSTORE <destination> <numkeys> <key> [<key> ...]
  [WEIGHTS <weight> [<weight> ...]]
  [AGGREGATE SUM | MIN | MAX]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<destination>` | Yes | No | Redis key used as destination. |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | Yes | Yes | Redis key targeted by the command. |
| `WEIGHTS <weight>` | No | Yes | Multiplier applied to each input key's scores, in the order the keys were given. |
| `(AGGREGATE SUM \| MIN \| MAX)` | No | No | Under `AGGREGATE`, choose one form: `SUM`; `MIN`; `MAX`. |

## Important points

* `numkeys` must equal the number of key arguments that immediately follow it.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
ZUNIONSTORE destination-key 1 my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zadd(
    "key1",
    { score: 1, member: "member1" },
)
await redis.zadd(
    "key2",
    { score: 1, member: "member1" },
    { score: 2, member: "member2" },
)

const res = await redis.zunionstore("destination", 2, ["key1", "key2"]);
console.log(res) // 2
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.zunionstore("destination-key", ["my-key"])
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.zunionstore("destination-key", "1", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.zUnionStore("destination-key", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.zunionstore("destination-key", ["my-key"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.ZUnionStore(context.Background(), "destination-key", &redis.ZStore{Keys: []string{"my-key"}}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.zunionstore("destination-key", "my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.zunionstore("destination-key", &["my-key"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# Streams commands
Source: https://upstash.com/docs/redis/commands/streams/overview

<CardGroup cols={2}>
<Card title="XACK" href="/redis/commands/streams/xack">Acknowledge stream messages</Card>
<Card title="XACKDEL" href="/redis/commands/streams/xackdel">Acknowledge and delete messages</Card>
<Card title="XADD" href="/redis/commands/streams/xadd">Add entry to stream</Card>
<Card title="XAUTOCLAIM" href="/redis/commands/streams/xautoclaim">Auto-claim idle messages</Card>
<Card title="XCLAIM" href="/redis/commands/streams/xclaim">Claim pending messages</Card>
<Card title="XDEL" href="/redis/commands/streams/xdel">Delete stream entries</Card>
<Card title="XDELEX" href="/redis/commands/streams/xdelex">Delete entries with reference control</Card>
<Card title="XGROUP" href="/redis/commands/streams/xgroup">Manage consumer groups</Card>
<Card title="XINFO CONSUMERS" href="/redis/commands/streams/xinfo-consumers">List group consumers</Card>
<Card title="XINFO GROUPS" href="/redis/commands/streams/xinfo-groups">List stream groups</Card>
<Card title="XINFO STREAM" href="/redis/commands/streams/xinfo-stream">Get stream info</Card>
<Card title="XLEN" href="/redis/commands/streams/xlen">Get stream length</Card>
<Card title="XPENDING" href="/redis/commands/streams/xpending">Get pending messages info</Card>
<Card title="XRANGE" href="/redis/commands/streams/xrange">Get range of stream entries</Card>
<Card title="XREAD" href="/redis/commands/streams/xread">Read stream entries</Card>
<Card title="XREADGROUP" href="/redis/commands/streams/xreadgroup">Read as consumer group</Card>
<Card title="XREVRANGE" href="/redis/commands/streams/xrevrange">Get range in reverse order</Card>
<Card title="XTRIM" href="/redis/commands/streams/xtrim">Trim stream to max length</Card>
</CardGroup>

# XACK
Source: https://upstash.com/docs/redis/commands/streams/xack

Use `XACK` to tell a consumer group that entries have been processed.

Acknowledged entries leave the group's pending entries list, so they are no longer counted as in flight and can no longer be claimed by another consumer. The reply counts the IDs that were actually pending, which means acknowledging twice is harmless and returns `0` the second time.

Acknowledging does not delete the entry from the stream: other groups still see it, and it stays until trimmed. Use [`XACKDEL`](/docs/redis/commands/streams/xackdel) when the entry should also be removed.

## Syntax

```redis
XACK <key> <group> <ID> [<ID> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<group>` | Yes | No | Consumer group name. |
| `<ID>` | Yes | Yes | Stream entry ID. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XACK my-key workers 0-0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const result = await redis.xack("mystream", "mygroup", "1638360173533-0");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xack("my-key", "workers", "0-0")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xack("my-key", "workers", "0-0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xAck("my-key", "workers", "0-0");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xack("my-key", "workers", "0-0")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XAck(context.Background(), "my-key", "workers", "0-0").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xack("my-key", "workers", new redis.clients.jedis.StreamEntryID("0-0"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xack("my-key", "workers", &["0-0"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XACKDEL
Source: https://upstash.com/docs/redis/commands/streams/xackdel

Use `XACKDEL` to acknowledge entries in a consumer group and delete them from the stream in one atomic step.

It combines what [`XACK`](/docs/redis/commands/streams/xack) and [`XDEL`](/docs/redis/commands/streams/xdel) do, which is what you want when a stream serves a single group and processed entries have no reason to stay around. `IDS <numids>` introduces the list of IDs and the count must match.

The reference policy controls what happens to consumer group references of the deleted entries: `KEEPREF`, the default, leaves references in other groups' pending lists in place, `DELREF` removes them everywhere, and `ACKED` only deletes entries that every group has read and acknowledged, leaving the rest in the stream. The reply holds one status code per requested ID, in order.

## Syntax

```redis
XACKDEL <key> <group>
  [KEEPREF | DELREF | ACKED]
  IDS <numids> <id> [<id> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<group>` | Yes | No | Consumer group name. |
| `(KEEPREF \| DELREF \| ACKED)` | No | No | What happens to consumer-group references of the deleted entries: `KEEPREF` (the default) leaves them in place, `DELREF` removes them from every group's pending list, and `ACKED` only removes entries that every group has read and acknowledged. |
| `IDS <numids> <id> [<id> ...]` | Yes | No | Entry IDs to target. Give the ID count first, then that many IDs. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integer status codes, one per ID |
| RESP3 | Array of integer status codes, one per ID |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XACKDEL my-key workers IDS 1 0-0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

// Acknowledge and delete a single entry
const result = await redis.xackdel("mystream", "mygroup", "1638360173533-0");
console.log(result); // Array of results for each ID
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xackdel("my-key", "workers", "0-0")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("XACKDEL", "my-key", "workers", "IDS", "1", "0-0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xAckDel("my-key", "workers", "0-0");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xackdel("my-key", "workers", "0-0")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XAckDel(context.Background(), "my-key", "workers", "KEEPREF", "0-0").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xackdel("my-key", "workers", new redis.clients.jedis.StreamEntryID("0-0"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xack_del("my-key", "workers", &["0-0"], redis::streams::StreamDeletionPolicy::KeepRef)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XADD
Source: https://upstash.com/docs/redis/commands/streams/xadd

Use `XADD` to append an entry to a stream, creating the stream when it does not exist.

An entry is a set of field and value pairs identified by an ID of the form `<milliseconds>-<sequence>`. Passing `*` lets the server build the ID from the current time, which guarantees that IDs only ever increase; an explicit ID must be greater than the last one in the stream. The reply is the ID the entry was stored under. `NOMKSTREAM` skips creating a stream that does not exist yet and returns null instead.

The trimming options cap the stream in the same call, which is how a stream is kept from growing without bound. `MAXLEN` limits the number of entries and `MINID` drops entries with an ID below a threshold, which is the way to trim by age since IDs start with a timestamp. `~` makes the trim approximate, stopping at a convenient boundary, which is much cheaper than the exact `=` and is what most workloads should use; `LIMIT` caps how many entries a single call may evict.

`KEEPREF`, `DELREF`, and `ACKED` decide what happens to consumer group references of the entries that trimming removes: `KEEPREF`, the default, leaves those references in place, `DELREF` removes them as well, and `ACKED` only removes entries that every group has read and acknowledged.

## Syntax

```redis
XADD <key>
  [NOMKSTREAM]
  [KEEPREF | DELREF | ACKED]
  [(MAXLEN | MINID) [= | ~] <threshold> [LIMIT <count>]]
  (* | <id>) <field> <value> [<field> <value> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `NOMKSTREAM` | No | No | Do not create a missing stream. |
| `(KEEPREF \| DELREF \| ACKED)` | No | No | What happens to consumer-group references of the entries that trimming removes: `KEEPREF` (the default) leaves them in place, `DELREF` removes them from every group's pending list, and `ACKED` only removes entries that every group has read and acknowledged. |
| `(MAXLEN \| MINID) [= \| ~] <threshold> [LIMIT <count>]` | No | No | Trim the stream after the entry is added. `MAXLEN` caps the number of entries; `MINID` drops entries with a lower ID. `=` trims exactly and is the default; `~` trims approximately and is required before `LIMIT`, which caps how many entries a single call evicts. |
| `(* \| <id>)` | Yes | No | Entry ID: `*` lets the server generate one from the current time, or give an explicit `<milliseconds>-<sequence>` ID, which must be greater than the stream's last one. |
| `<field> <value>` | Yes | Yes | Field and the value to store in it. Repeat to set several fields in one call. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string or Null bulk string or null array |
| RESP3 | Bulk string or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XADD my-key * field value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const result = await redis.xadd("mystream", "*", { name: "John Doe", age: 30 });
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xadd("my-key", "*", {"field": "value"})
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xadd("my-key", "*", "field", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xAdd("my-key", "*", { field: "value" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xadd("my-key", {"field": "value"})
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XAdd(context.Background(), &redis.XAddArgs{Stream: "my-key", Values: map[string]interface{}{"field": "value"}}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xadd("my-key", redis.clients.jedis.StreamEntryID.NEW_ENTRY, java.util.Map.of("field", "value"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xadd("my-key", "*", &[("field", "value")])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XAUTOCLAIM
Source: https://upstash.com/docs/redis/commands/streams/xautoclaim

Use `XAUTOCLAIM` to transfer ownership of pending entries that have been idle for too long, without having to name their IDs.

Starting from `<start>`, it walks the group's pending entries list and claims for `<consumer>` every entry that has been idle at least `<min-idle-time>` milliseconds, up to `COUNT` of them. The reply has three parts: a cursor to pass as `<start>` on the next call, the claimed entries themselves (or only their IDs with `JUSTID`), and the IDs that were dropped from the pending list because they no longer exist in the stream.

This is the recommended way to recover work from a consumer that crashed or stalled: it replaces the older loop of [`XPENDING`](/docs/redis/commands/streams/xpending) to find idle entries followed by [`XCLAIM`](/docs/redis/commands/streams/xclaim) to take them, and it can be run repeatedly by a janitor task, since the idle threshold keeps healthy consumers' work untouched.

## Syntax

```redis
XAUTOCLAIM <key> <group> <consumer> <min-idle-time> <start> [COUNT <count>] [JUSTID]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<group>` | Yes | No | Consumer group name. |
| `<consumer>` | Yes | No | Consumer name within the group. |
| `<min-idle-time>` | Yes | No | Only claim entries that have been idle at least this many milliseconds. |
| `<start>` | Yes | No | ID to start scanning the pending list from; `0-0` starts at the beginning. |
| `COUNT <count>` | No | No | Maximum number of entries to claim. |
| `JUSTID` | No | No | Return only entry IDs, without their fields and values. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Three-element array: next ID, claimed entries or IDs, and deleted IDs |
| RESP3 | Three-element array: next ID, claimed entries or IDs, and deleted IDs |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XAUTOCLAIM my-key workers worker-1 60000 0-0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const result = await redis.xautoclaim(
  "mystream",
  "mygroup",
  "consumer1",
  60000,
  "0-0"
);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xautoclaim("my-key", "workers", "worker-1", 60000, "0-0")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xautoclaim("my-key", "workers", "worker-1", "60000", "0-0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xAutoClaim("my-key", "workers", "worker-1", 60000, "0-0");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xautoclaim("my-key", "workers", "worker-1", 60000, "0-0")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, _, err := client.XAutoClaim(context.Background(), &redis.XAutoClaimArgs{Stream: "my-key", Group: "workers", Consumer: "worker-1", MinIdle: time.Minute, Start: "0-0"}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xautoclaim("my-key", "workers", "worker-1", 60000, new redis.clients.jedis.StreamEntryID("0-0"), redis.clients.jedis.params.XAutoClaimParams.xAutoClaimParams());
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::streams::StreamAutoClaimOptions;
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xautoclaim_options(
        "my-key",
        "workers",
        "worker-1",
        60000,
        "0-0",
        StreamAutoClaimOptions::default(),
    )?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XCLAIM
Source: https://upstash.com/docs/redis/commands/streams/xclaim

Use `XCLAIM` to change the owner of specific pending entries in a consumer group.

The entries must have been idle for at least `<min-idle-time>` milliseconds, which is what keeps two recovery attempts from stealing the same work from each other: the first claim resets the idle time and the second one then finds nothing to take. Claiming resets the entry's delivery time and increments its delivery counter, unless `JUSTID` is used, which returns only the IDs and leaves the counter alone.

`IDLE` and `TIME` set the new idle time or the last delivery time explicitly, `RETRYCOUNT` overrides the delivery counter, and `FORCE` creates a pending entry for IDs that exist in the stream but are not currently pending. Entries that no longer exist in the stream are removed from the pending list instead of being claimed.

When you want to claim whatever is idle rather than specific IDs, use [`XAUTOCLAIM`](/docs/redis/commands/streams/xautoclaim).

## Syntax

```redis
XCLAIM <key> <group> <consumer> <min-idle-time> <ID> [<ID> ...]
  [IDLE <ms>]
  [TIME <unix-time-milliseconds>]
  [RETRYCOUNT <count>]
  [FORCE]
  [JUSTID]
  [LASTID <lastid>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<group>` | Yes | No | Consumer group name. |
| `<consumer>` | Yes | No | Consumer name within the group. |
| `<min-idle-time>` | Yes | No | Only claim entries that have been idle at least this many milliseconds. |
| `<ID>` | Yes | Yes | Stream entry ID. |
| `IDLE <ms>` | No | No | Set the entry's idle time to this many milliseconds. |
| `TIME <unix-time-milliseconds>` | No | No | Set the entry's last-delivery time to this Unix timestamp in milliseconds. |
| `RETRYCOUNT <count>` | No | No | Set the entry's delivery counter to this value. |
| `FORCE` | No | No | Create the pending entry even when the ID is not in the group's pending list. |
| `JUSTID` | No | No | Return only entry IDs, without their fields and values. |
| `LASTID <lastid>` | No | No | New last-delivered ID for the group. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of entry arrays, or array of IDs with `JUSTID` |
| RESP3 | Array of entry arrays, or array of IDs with `JUSTID` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XCLAIM my-key workers worker-1 60000 0-0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const result = await redis.xclaim(
  "mystream",
  "mygroup",
  "consumer1",
  60000,
  ["1638360173533-0", "1638360173533-1"]
);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xclaim("my-key", "workers", "worker-1", 60000, "0-0")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xclaim("my-key", "workers", "worker-1", "60000", "0-0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xClaim("my-key", "workers", "worker-1", 60000, "0-0");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xclaim("my-key", "workers", "worker-1", 60000, ["0-0"])
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XClaim(context.Background(), &redis.XClaimArgs{Stream: "my-key", Group: "workers", Consumer: "worker-1", MinIdle: time.Minute, Messages: []string{"0-0"}}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xclaim("my-key", "workers", "worker-1", 60000, redis.clients.jedis.params.XClaimParams.xClaimParams(), new redis.clients.jedis.StreamEntryID("0-0"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xclaim("my-key", "workers", "worker-1", 60000, &["0-0"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XDEL
Source: https://upstash.com/docs/redis/commands/streams/xdel

Use `XDEL` to remove entries from a stream by ID.

The reply counts the entries that were actually removed. Deleting an entry does not touch consumer group state: an entry already delivered stays in a group's pending entries list until it is acknowledged, so it can still show up in [`XPENDING`](/docs/redis/commands/streams/xpending) after being deleted. Use [`XDELEX`](/docs/redis/commands/streams/xdelex) when you need control over those references.

IDs are never reused, so the stream's ID sequence keeps moving forward after deletions. For bulk cleanup by size or age, [`XTRIM`](/docs/redis/commands/streams/xtrim) is the right command.

## Syntax

```redis
XDEL <key> <ID> [<ID> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<ID>` | Yes | Yes | Stream entry ID. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XDEL my-key 0-0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const result = await redis.xdel("mystream", "1638360173533-0");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xdel("my-key", "0-0")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xdel("my-key", "0-0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xDel("my-key", "0-0");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xdel("my-key", "0-0")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XDel(context.Background(), "my-key", "0-0").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xdel("my-key", new redis.clients.jedis.StreamEntryID("0-0"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xdel("my-key", &["0-0"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XDELEX
Source: https://upstash.com/docs/redis/commands/streams/xdelex

Use `XDELEX` to delete entries from a stream with control over what happens to consumer group references.

`IDS <numids>` introduces the list of IDs and the count must match. The policy decides how consumer groups are affected: `KEEPREF`, the default, deletes the entries but leaves them in the pending entries lists of the groups that had received them, `DELREF` removes those references as well, and `ACKED` only deletes entries that every group has read and acknowledged, leaving the others in place.

That last policy is what makes the command useful as a safe cleanup step: it removes exactly the entries nobody still owes work on. The reply holds one status code per requested ID. Where [`XDEL`](/docs/redis/commands/streams/xdel) always behaves like `KEEPREF`, `XDELEX` is its explicit form.

## Syntax

```redis
XDELEX <key>
  [KEEPREF | DELREF | ACKED]
  IDS <numids> <id> [<id> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `(KEEPREF \| DELREF \| ACKED)` | No | No | What happens to consumer-group references of the deleted entries: `KEEPREF` (the default) leaves them in place, `DELREF` removes them from every group's pending list, and `ACKED` only removes entries that every group has read and acknowledged. |
| `IDS <numids> <id> [<id> ...]` | Yes | No | Entry IDs to target. Give the ID count first, then that many IDs. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of integer status codes, one per ID |
| RESP3 | Array of integer status codes, one per ID |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XDELEX my-key IDS 1 0-0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

// Delete a single stream entry
const result = await redis.xdelex("mystream", "1638360173533-0");
console.log(result); // Array of results for each ID
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xdelex("my-key", "0-0")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xdelex("my-key", "IDS", "1", "0-0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xDelEx("my-key", "0-0");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xdelex("my-key", "0-0")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XDelEx(context.Background(), "my-key", "KEEPREF", "0-0").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xdelex("my-key", new redis.clients.jedis.StreamEntryID("0-0"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xdel_ex("my-key", &["0-0"], redis::streams::StreamDeletionPolicy::KeepRef)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XGROUP
Source: https://upstash.com/docs/redis/commands/streams/xgroup

Use `XGROUP` to manage the consumer groups of a stream.

`CREATE` registers a group and sets the position it starts reading from: `0` replays the whole stream from the beginning, while `$` delivers only entries added after the group was created. `MKSTREAM` creates the stream too when it does not exist yet, which avoids an error on a group created before the first producer runs. `SETID` moves that position afterwards, which is how a group is rewound for a replay or fast-forwarded past a backlog.

`CREATECONSUMER` and `DELCONSUMER` add and remove consumers explicitly; consumers are otherwise created on first read. Deleting a consumer returns how many pending entries went away with it, and those entries are then no longer pending for anyone, so hand them over with [`XCLAIM`](/docs/redis/commands/streams/xclaim) or [`XAUTOCLAIM`](/docs/redis/commands/streams/xautoclaim) first if the work still matters.

`DESTROY` removes a group along with all of its pending state, while leaving the stream and its entries untouched.

## Syntax

```redis
XGROUP CREATE <key> <group> <id | $> [MKSTREAM]
XGROUP CREATECONSUMER <key> <group> <consumer>
XGROUP DELCONSUMER <key> <group> <consumer>
XGROUP DESTROY <key> <group>
XGROUP SETID <key> <group> <id | $>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `subcommand` | Yes | No | Consumer-group operation shown in the syntax block. |
| `key` | Yes | No | Stream key. |
| `group` | Yes | No | Consumer-group name. |
| `consumer` | Some forms | No | Consumer name for CREATECONSUMER or DELCONSUMER. |
| `id \| $` | Some forms | No | Starting or replacement last-delivered ID; `$` means the stream's latest ID. |
| `MKSTREAM` | No | No | Create an empty stream when CREATE targets a missing key. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` for `CREATE` and `SETID`; Integer for `CREATECONSUMER`, `DELCONSUMER`, and `DESTROY` |
| RESP3 | Simple string `OK` for `CREATE` and `SETID`; Integer for `CREATECONSUMER`, `DELCONSUMER`, and `DESTROY` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XGROUP CREATE events workers $ MKSTREAM
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const result = await redis.xgroup("mystream", {
  type: "CREATE",
  group: "mygroup",
  id: "$",
  options: { MKSTREAM: true }
});
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xgroup_create("events", "workers", "$", mkstream=True)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xgroup("CREATE", "events", "workers", "$", "MKSTREAM");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xGroupCreate("events", "workers", "$", { MKSTREAM: true });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xgroup_create("events", "workers", "$", mkstream=True)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XGroupCreateMkStream(context.Background(), "events", "workers", "$").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xgroupCreate("events", "workers", redis.clients.jedis.StreamEntryID.LAST_ENTRY, true);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xgroup_create_mkstream("events", "workers", "$")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XINFO CONSUMERS
Source: https://upstash.com/docs/redis/commands/streams/xinfo-consumers

Use `XINFO CONSUMERS` to list the consumers of a consumer group.

For each consumer the reply gives its name, how many entries it currently has pending, and how long it has been idle or inactive. This is the view that tells you whether workers are alive and balanced: a consumer with a growing pending count and a large idle time is one that stopped without acknowledging its work, and its entries are the ones to recover with [`XAUTOCLAIM`](/docs/redis/commands/streams/xautoclaim).

## Syntax

```redis
XINFO CONSUMERS <key> <group>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<group>` | Yes | No | Consumer group name. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of flat consumer-metadata arrays |
| RESP3 | Array of flat consumer-metadata arrays |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XINFO CONSUMERS my-key workers
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.xinfo("my-key", {
  type: "CONSUMERS",
  group: "workers",
});
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xinfo_consumers("my-key", "workers")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xinfo("CONSUMERS", "my-key", "workers");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xInfoConsumers("my-key", "workers");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xinfo_consumers("my-key", "workers")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XInfoConsumers(context.Background(), "my-key", "workers").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xinfoConsumers("my-key", "workers");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xinfo_consumers("my-key", "workers")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XINFO GROUPS
Source: https://upstash.com/docs/redis/commands/streams/xinfo-groups

Use `XINFO GROUPS` to list the consumer groups defined on a stream.

Each group is reported with its name, its number of consumers, how many entries are pending across the group, the ID it has delivered up to, and its lag, meaning how many entries in the stream it has not read yet. Lag and pending count together are the standard health signals for a stream consumer: a growing lag means consumers cannot keep up, while a growing pending count means they read entries but do not acknowledge them.

## Syntax

```redis
XINFO GROUPS <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of flat group-metadata arrays |
| RESP3 | Array of flat group-metadata arrays |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XINFO GROUPS my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.xinfo("my-key", { type: "GROUPS" });
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xinfo_groups("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xinfo("GROUPS", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xInfoGroups("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xinfo_groups("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XInfoGroups(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xinfoGroups("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xinfo_groups("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XINFO STREAM
Source: https://upstash.com/docs/redis/commands/streams/xinfo-stream

Use `XINFO STREAM` to get information about a stream.

The default reply summarizes the stream: how many entries it holds, the last generated ID, the number of consumer groups, and the first and last entries. `FULL` returns the detailed form instead, walking the entries along with every group, consumer, and pending entry, with `COUNT` limiting how many items each part of that reply contains (`COUNT 0` means no limit).

Use the summary for monitoring and the full form when debugging a group that appears stuck, since it shows exactly which entries are pending and who owns them.

## Syntax

```redis
XINFO STREAM <key> [FULL [COUNT <count>]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `FULL [COUNT <count>]` | No | No | Return the full report, including entries and consumer-group state. `COUNT` caps how many entries each section lists; `0` returns all of them. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Flat array of alternating keys and values |
| RESP3 | Map |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XINFO STREAM my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xinfo("STREAM", "my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xInfoStream("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xinfo_stream("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XInfoStream(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xinfoStream("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xinfo_stream("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XLEN
Source: https://upstash.com/docs/redis/commands/streams/xlen

Use `XLEN` to get the number of entries currently in a stream.

Deleted and trimmed entries are not counted, and the reply is `0` when the key does not exist. The number reflects what is stored right now, not how many entries the stream has ever carried, so it is a measure of retained data rather than of throughput; a group's backlog is reported by the lag in [`XINFO GROUPS`](/docs/redis/commands/streams/xinfo-groups).

## Syntax

```redis
XLEN <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XLEN my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const result = await redis.xlen("mystream");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xlen("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xlen("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xLen("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xlen("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XLen(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xlen("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xlen("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XPENDING
Source: https://upstash.com/docs/redis/commands/streams/xpending

Use `XPENDING` to inspect the entries that a consumer group has delivered but not yet acknowledged.

With only a key and a group, the reply is a summary: how many entries are pending, the lowest and highest pending IDs, and the number of pending entries per consumer. That is the cheap form to poll for monitoring.

The extended form, with a start, an end, and a count, lists the pending entries one by one with the consumer that owns each, how long it has been idle, and how many times it has been delivered. `IDLE` restricts the listing to entries idle for at least the given number of milliseconds, and a consumer name restricts it to one worker.

A high delivery count points at an entry that keeps failing, and a long idle time points at a consumer that died holding work. Both are handed over to another consumer with [`XCLAIM`](/docs/redis/commands/streams/xclaim) or [`XAUTOCLAIM`](/docs/redis/commands/streams/xautoclaim).

## Syntax

```redis
XPENDING <key> <group>
  [[IDLE <min-idle-time>]
    <start> <end> <count>
    [<consumer>]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<group>` | Yes | No | Consumer group name. |
| `[IDLE <min-idle-time>] <start> <end> <count> [<consumer>]` | No | No | Extended form: optionally filter by idle time, then give the ID range, the maximum number of entries to return, and optionally a single consumer to report on. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Four-element summary array, or array of pending-entry detail arrays |
| RESP3 | Four-element summary array, or array of pending-entry detail arrays |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XPENDING my-key workers
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const result = await redis.xpending("mystream", "mygroup", "-", "+", 10);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xpending("my-key", "workers")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xpending("my-key", "workers");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xPending("my-key", "workers");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xpending("my-key", "workers")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XPending(context.Background(), "my-key", "workers").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xpending("my-key", "workers");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xpending("my-key", "workers")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XRANGE
Source: https://upstash.com/docs/redis/commands/streams/xrange

Use `XRANGE` to read the entries of a stream within a range of IDs, from the oldest to the newest.

`-` and `+` stand for the smallest and largest possible IDs, so `XRANGE key - +` reads the whole stream. An incomplete ID such as `1526985054069` is completed with the smallest possible sequence number for `<start>` and the largest for `<end>`, which makes it easy to select everything within one millisecond. Prefixing a bound with `(` makes it exclusive, and `COUNT` limits how many entries come back.

Since IDs start with a timestamp, ranges are time queries: pass the milliseconds you care about and you get the entries recorded in that window. Paging works by repeating the call with the last ID you received, made exclusive with `(`. Use [`XREVRANGE`](/docs/redis/commands/streams/xrevrange) to walk the stream backwards.

## Syntax

```redis
XRANGE <key> <start> <end> [COUNT <count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<start>` | Yes | No | First ID to return; `-` means the smallest possible ID. |
| `<end>` | Yes | No | Last ID to return; `+` means the largest possible ID. |
| `COUNT <count>` | No | No | Maximum number of entries to return. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of two-element entry arrays: ID and flat field/value array |
| RESP3 | Array of two-element entry arrays: ID and flat field/value array |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XRANGE my-key - +
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const result = await redis.xrange("mystream", "-", "+");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xrange("my-key", "-", "+")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xrange("my-key", "-", "+");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xRange("my-key", "-", "+");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xrange("my-key", "-", "+")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XRange(context.Background(), "my-key", "-", "+").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xrange("my-key", "-", "+");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xrange("my-key", "-", "+")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XREAD
Source: https://upstash.com/docs/redis/commands/streams/xread

Use `XREAD` to read entries from one or more streams that are newer than a given ID.

After `STREAMS`, all keys are listed first and then exactly one ID for each key, in the same order. Only entries with an ID greater than the one you give are returned, and the special ID `$` means "only entries added after this call", which is how a client starts tailing a live stream.

`BLOCK <milliseconds>` waits for new data instead of returning an empty reply, with `0` waiting indefinitely, which turns the command into an efficient follow rather than a polling loop. `COUNT` caps how many entries per stream come back.

`XREAD` keeps no server-side position: the client remembers the last ID it processed and passes it on the next call, and every reader sees every entry. When work should be split across consumers, with the server tracking what has been delivered and acknowledged, use a consumer group and [`XREADGROUP`](/docs/redis/commands/streams/xreadgroup).

## Syntax

```redis
XREAD
  [COUNT <count>]
  [BLOCK <milliseconds>]
  STREAMS <key> [<key> ...] <ID> [<ID> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `COUNT <count>` | No | No | Maximum number of entries to return per stream. |
| `BLOCK <milliseconds>` | No | No | Milliseconds to block waiting for new entries; `0` blocks indefinitely. |
| `STREAMS <key> [<key> ...] <ID> [<ID> ...]` | Yes | No | Streams to read. List every key first, then one ID per key in the same order. |

## Important points

* A blocking form holds the request until data arrives or its timeout expires. Set the client/network timeout longer than the command timeout.
* After `STREAMS`, provide all keys first and then exactly one ID for each key, in the same order.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Flat array of alternating keys and values or Null bulk string or null array |
| RESP3 | Map or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XREAD STREAMS my-key 0-0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const result = await redis.xread("mystream", "0-0");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xread({"my-key": "0-0"})
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xread("STREAMS", "my-key", "0-0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xRead({ key: "my-key", id: "0-0" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xread({"my-key": "0-0"})
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XRead(context.Background(), &redis.XReadArgs{Streams: []string{"my-key", "0-0"}}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xread(redis.clients.jedis.params.XReadParams.xReadParams(), java.util.Map.of("my-key", new redis.clients.jedis.StreamEntryID("0-0")));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xread(&["my-key"], &["0-0"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XREADGROUP
Source: https://upstash.com/docs/redis/commands/streams/xreadgroup

Use `XREADGROUP` to read entries from a stream as a member of a consumer group, so that each entry goes to one consumer and Redis keeps track of what has not been acknowledged.

After `STREAMS`, all keys come first and then one ID per key. The special ID `>` delivers entries that have never been delivered to the group and records them as pending for this consumer. Any other ID re-reads that consumer's own pending entries instead, starting after the given ID, which is how a consumer resumes after a restart: read from `0` first to finish old work, then switch to `>`.

The consumer is created on first use. `COUNT` limits the batch size and `BLOCK <milliseconds>` waits for new entries rather than returning empty, with `0` waiting indefinitely. `NOACK` skips the pending entries list entirely, trading the delivery guarantee for speed.

Entries stay pending until acknowledged with [`XACK`](/docs/redis/commands/streams/xack). That is what makes recovery possible: work left behind by a crashed consumer is visible in [`XPENDING`](/docs/redis/commands/streams/xpending) and can be taken over with [`XAUTOCLAIM`](/docs/redis/commands/streams/xautoclaim).

## Syntax

```redis
XREADGROUP GROUP <group> <consumer>
  [COUNT <count>]
  [BLOCK <milliseconds>]
  [NOACK]
  STREAMS <key> [<key> ...] <ID> [<ID> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `GROUP <group> <consumer>` | Yes | No | Consumer group to read from, and the consumer within it that claims the entries. |
| `COUNT <count>` | No | No | Maximum number of entries to return per stream. |
| `BLOCK <milliseconds>` | No | No | Milliseconds to block waiting for new entries; `0` blocks indefinitely. |
| `NOACK` | No | No | Do not add delivered entries to the pending list. |
| `STREAMS <key> [<key> ...] <ID> [<ID> ...]` | Yes | No | Streams to read. List every key first, then one ID per key in the same order. |

## Important points

* A blocking form holds the request until data arrives or its timeout expires. Set the client/network timeout longer than the command timeout.
* After `STREAMS`, provide all keys first and then exactly one ID for each key, in the same order.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Null bulk string or null array or Flat array of alternating keys and values |
| RESP3 | Null or Map |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XREADGROUP GROUP workers worker-1 STREAMS my-key 0-0
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const result = await redis.xreadgroup("mygroup", "consumer1", "mystream", ">");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xreadgroup("workers", "worker-1", {"my-key": "0-0"})
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xreadgroup("GROUP", "workers", "worker-1", "STREAMS", "my-key", "0-0");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xReadGroup("workers", "worker-1", { key: "my-key", id: "0-0" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xreadgroup("workers", "worker-1", {"my-key": "0-0"})
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XReadGroup(context.Background(), &redis.XReadGroupArgs{Group: "workers", Consumer: "worker-1", Streams: []string{"my-key", "0-0"}}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xreadGroup("workers", "worker-1", redis.clients.jedis.params.XReadGroupParams.xReadGroupParams(), java.util.Map.of("my-key", new redis.clients.jedis.StreamEntryID("0-0")));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let mut command = redis::cmd("XREADGROUP");
    command.arg("GROUP");
    command.arg("workers");
    command.arg("worker-1");
    command.arg("STREAMS");
    command.arg("my-key");
    command.arg("0-0");
    let result: redis::Value = command.query(&mut connection)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XREVRANGE
Source: https://upstash.com/docs/redis/commands/streams/xrevrange

Use `XREVRANGE` to read the entries of a stream within a range of IDs, from the newest to the oldest.

It is [`XRANGE`](/docs/redis/commands/streams/xrange) in reverse, and the bounds are given in reverse too: `<end>` comes before `<start>`, so `XREVRANGE key + -` walks the whole stream backwards. Incomplete IDs are completed the same way, `(` makes a bound exclusive, and `COUNT` limits the number of entries.

`XREVRANGE key + - COUNT 1` is the idiomatic way to read the most recent entry of a stream.

## Syntax

```redis
XREVRANGE <key> <end> <start> [COUNT <count>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<end>` | Yes | No | Last ID to return, scanning backwards; `-` means the smallest possible ID. |
| `<start>` | Yes | No | First ID to return, scanning backwards; `+` means the largest possible ID. |
| `COUNT <count>` | No | No | Maximum number of entries to return. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of two-element entry arrays: ID and flat field/value array |
| RESP3 | Array of two-element entry arrays: ID and flat field/value array |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XREVRANGE my-key + -
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const result = await redis.xrevrange("mystream", "+", "-");
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xrevrange("my-key", "+", "-")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xrevrange("my-key", "+", "-");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xRevRange("my-key", "+", "-");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xrevrange("my-key", "+", "-")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XRevRange(context.Background(), "my-key", "+", "-").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xrevrange("my-key", "+", "-");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xrevrange("my-key", "+", "-")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# XTRIM
Source: https://upstash.com/docs/redis/commands/streams/xtrim

Use `XTRIM` to cap the size of a stream by removing old entries.

`MAXLEN` keeps at most the given number of entries, dropping the oldest ones, while `MINID` drops every entry with an ID below a threshold, which is how you trim by age since IDs begin with a millisecond timestamp. The reply is the number of entries removed.

`~` makes the trim approximate: the server stops at a convenient boundary and may leave a few extra entries, which is much cheaper on large streams and is the right default for background maintenance. `=` trims exactly, and `LIMIT` caps how many entries a single call may evict, which keeps one call from blocking for a long time.

`KEEPREF`, `DELREF`, and `ACKED` decide what happens to consumer group references of the removed entries: `KEEPREF`, the default, leaves them in place, `DELREF` removes them from every group's pending list, and `ACKED` only removes entries that all groups have read and acknowledged. [`XADD`](/docs/redis/commands/streams/xadd) accepts the same options so a stream can be trimmed as it is written to.

## Syntax

```redis
XTRIM <key>
  (MAXLEN | MINID) [= | ~] <threshold>
  [LIMIT <count>]
  [KEEPREF | DELREF | ACKED]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `(MAXLEN \| MINID) [= \| ~] <threshold> [LIMIT <count>] [KEEPREF \| DELREF \| ACKED]` | Yes | No | Trim the stream. `MAXLEN` caps the number of entries; `MINID` drops entries with a lower ID. `=` trims exactly and is the default; `~` trims approximately and is required before `LIMIT`, which caps how many entries a single call evicts. `KEEPREF` (the default) leaves consumer-group references to the deleted entries in place, `DELREF` removes those references too, and `ACKED` only removes entries that every group has read and acknowledged. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
XTRIM my-key MAXLEN 100
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const result = await redis.xtrim("mystream", {
  strategy: "MAXLEN",
  threshold: 100,
  exactness: "~"
});
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.xtrim("my-key", maxlen=100)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.xtrim("my-key", "MAXLEN", "100");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.xTrim("my-key", "MAXLEN", 100);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.xtrim("my-key", maxlen=100)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.XTrimMaxLen(context.Background(), "my-key", 100).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.xtrim("my-key", redis.clients.jedis.params.XTrimParams.xTrimParams().maxLen(100));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.xtrim("my-key", redis::streams::StreamMaxlen::Equals(100))?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# APPEND
Source: https://upstash.com/docs/redis/commands/string/append

Use `APPEND` to add a value to the end of the string stored at a key.

If the key does not exist it is created with the given value, so the command behaves like [`SET`](/docs/redis/commands/string/set) on a fresh key. The reply is the length of the string after the append. Redis grows the underlying buffer with room to spare, so repeated appends stay cheap, which makes this a reasonable way to accumulate log lines or serialized events into one value; [`GETRANGE`](/docs/redis/commands/string/getrange) can then read back a slice without transferring the whole thing.

## Syntax

```redis
APPEND <key> <value>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<value>` | Yes | No | String appended to the current value. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
APPEND my-key value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.append(key, "Hello");
// returns 5
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.append("my-key", "value")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.append("my-key", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.append("my-key", "value");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.append("my-key", "value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Append(context.Background(), "my-key", "value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.append("my-key", "value");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.append("my-key", "value")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# DECR
Source: https://upstash.com/docs/redis/commands/string/decr

Use `DECR` to subtract one from the integer stored at a key and get the result.

A missing key counts as `0`, so the first call leaves `-1`. The value must be the string form of a 64-bit signed integer; anything else returns an error, as does an operation that would go below the minimum. The read, the subtraction, and the write happen atomically, so concurrent callers each get a distinct value and no decrement is lost, which is what makes it safe for counting down remaining quota or stock.

## Syntax

```redis
DECR <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
DECR my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.set("key", 6);
await redis.decr("key");
// returns 5
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.decr("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.decr("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.decr("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.decr("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Decr(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.decr("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.decr("my-key", 1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# DECRBY
Source: https://upstash.com/docs/redis/commands/string/decrby

Use `DECRBY` to subtract an integer from the value stored at a key and get the result.

A missing key counts as `0`. The stored value must be the string form of a 64-bit signed integer, and an operation that would exceed that range returns an error. The whole update is atomic, so concurrent callers cannot lose a decrement. Passing a negative amount adds instead, which makes [`INCRBY`](/docs/redis/commands/string/incrby) and `DECRBY` two views of the same operation.

## Syntax

```redis
DECRBY <key> <decrement>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<decrement>` | Yes | No | Amount to subtract from the current value. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
DECRBY my-key 1
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.set("key", 6);
await redis.decrby("key", 4);
// returns 2
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.decrby("my-key", 1)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.decrby("my-key", "1");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.decrBy("my-key", 1);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.decrby("my-key", 1)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.DecrBy(context.Background(), "my-key", 1).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.decrBy("my-key", 1);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.decr("my-key", 1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# DELEX
Source: https://upstash.com/docs/redis/commands/string/delex

Use `DELEX` to delete a string when a condition matches.

`IFEQ` deletes the key only when its current value equals the given one and `IFNE` only when it differs. `IFDEQ` and `IFDNE` do the same against a digest of the value, as returned by [`DIGEST`](/docs/redis/commands/string/digest), which avoids sending a large value over the wire just to compare it.

The comparison and the delete are one atomic step, which is what makes the command the correct way to release a lock or a claim: delete the key only if it still holds your token, so you never remove a lock that has expired and been taken by someone else. With no condition it simply deletes the key. The conditional forms require a string value, and a key of another type returns an error without being deleted.

## Syntax

```redis
DELEX <key> [IFEQ <value> | IFNE <value> | IFDEQ <digest> | IFDNE <digest>]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | String key to delete. |
| `IFEQ value \| IFNE value` | No | No | Delete when the stored string is equal to or different from the supplied value. |
| `IFDEQ digest \| IFDNE digest` | No | No | Delete when the stored string digest is equal to or different from the supplied 16-character digest. |

## Important points

* Supply at most one condition. With no condition, `DELEX` behaves like deleting a single key.
* Conditional forms accept string keys only. A non-string value returns an error and is not deleted.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
DELEX lock:job-1 IFEQ worker-1
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("DELEX", "lock:job-1", "IFEQ", "worker-1");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.delEx("lock:job-1", { condition: "IFEQ", matchValue: "worker-1" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.execute_command("DELEX", "lock:job-1", "IFEQ", "worker-1")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.DelExArgs(context.Background(), "lock:job-1", redis.DelExArgs{Mode: "IFEQ", MatchValue: "worker-1"}).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.delex("lock:job-1", redis.clients.jedis.util.CompareCondition.valueEq("worker-1"));
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.del_ex("lock:job-1", redis::ValueComparison::ifeq("worker-1"))?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# DIGEST
Source: https://upstash.com/docs/redis/commands/string/digest

Use `DIGEST` to get the digest of a string value.

The reply is a 16-character lowercase hexadecimal XXH3 digest of the stored bytes, null when the key does not exist, and an error when the value is not a string. It is a fast non-cryptographic hash, meant for change detection rather than for security.

Comparing digests lets a client tell whether a large value has changed without transferring it, for instance to decide whether a cached copy is still current. The same digest can be passed to the conditional forms of [`SET`](/docs/redis/commands/string/set) (`IFDEQ`, `IFDNE`) and [`DELEX`](/docs/redis/commands/string/delex), which turns it into a compact compare-and-set token for large values. `DIGEST` is an Upstash extension.

## Syntax

```redis
DIGEST <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `key` | Yes | No | String key whose value should be hashed. |

## Important points

* The reply is a 16-character lowercase hexadecimal XXH3 digest of the stored string bytes.
* A missing key returns null. A key containing a non-string value returns `WRONGTYPE`.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string or Null bulk string or null array |
| RESP3 | Bulk string or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
DIGEST lock:job-1
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

<Note>
  This command is not supported yet in `@upstash/redis`.
</Note>

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

<Note>
  This command is not supported yet in `upstash_redis`.
</Note>

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.call("DIGEST", "lock:job-1");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.digest("lock:job-1");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.execute_command("DIGEST", "lock:job-1")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Digest(context.Background(), "lock:job-1").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.digestKey("lock:job-1");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.digest("lock:job-1")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GET
Source: https://upstash.com/docs/redis/commands/string/get

Use `GET` to read the string value stored at a key.

The reply is null when the key does not exist, and an error when the key holds a value of another type, such as a hash or a list. Strings in Redis are binary safe and can hold anything up to 512 MB, so the same command reads back text, JSON, and serialized binary alike. To read and delete in one step use [`GETDEL`](/docs/redis/commands/string/getdel), and to read and refresh the expiration use [`GETEX`](/docs/redis/commands/string/getex).

## Syntax

```redis
GET <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string or Null bulk string or null array |
| RESP3 | Bulk string or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GET my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

type MyType = {
    a: number;
    b: string;
}
const value = await redis.get<MyType>("key");
if (!value) {
    // key doesn't exist
} else {
    // value is of type MyType
}
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.get("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.get("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.get("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.get("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Get(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.get("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.get("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GETDEL
Source: https://upstash.com/docs/redis/commands/string/getdel

Use `GETDEL` to read the value of a key and delete the key in the same atomic step.

The reply is the value, or null when the key does not exist, and an error when the value is not a string. Because nothing can run between the read and the delete, exactly one caller can obtain the value, which is what makes it right for one-shot data such as a one-time token, a password reset code, or a handoff between two processes. It replaces the [`GET`](/docs/redis/commands/string/get) plus [`DEL`](/docs/redis/commands/generic/del) pair and its race.

## Syntax

```redis
GETDEL <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string or Null bulk string or null array |
| RESP3 | Bulk string or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GETDEL my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

type MyType = {
    a: number;
    b: string;
}
await redis.getdel<MyType>("key");
// returns {a: 1, b: "2"}
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.getdel("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.getdel("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.getDel("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.getdel("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.GetDel(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.getDel("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.get_del("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GETEX
Source: https://upstash.com/docs/redis/commands/string/getex

Use `GETEX` to read a value and change its expiration in the same call.

`EX`, `PX`, `EXAT`, and `PXAT` set a new lifetime or deadline, and `PERSIST` removes the expiration so the key stops expiring. With no option at all the expiration is left exactly as it was, which is the difference from [`GET`](/docs/redis/commands/string/get) plus a separate [`EXPIRE`](/docs/redis/commands/generic/expire).

Doing both at once is what makes a sliding expiration correct: reading a session extends it atomically, with no window in which the key could expire between the read and the refresh.

## Syntax

```redis
GETEX <key>
  [EX <seconds> | PX <milliseconds> | EXAT <unix-time-seconds> |
    PXAT <unix-time-milliseconds> | PERSIST]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `(EX <seconds> \| PX <milliseconds> \| EXAT <unix-time-seconds> \| PXAT <unix-time-milliseconds> \| PERSIST)` | No | No | Choose one form: `EX` (set a lifetime in seconds); `PX` (set a lifetime in milliseconds); `EXAT` (expire at a Unix timestamp in seconds); `PXAT` (expire at a Unix timestamp in milliseconds); `PERSIST` (remove the expiration). Left unchanged when omitted. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string or Null bulk string or null array |
| RESP3 | Bulk string or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GETEX my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.getex("my-key");
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.getex("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.getex("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.getEx("my-key", { type: "PX", value: 1000 });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.getex("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.GetEx(context.Background(), "my-key", 0).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.GetExParams;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.getEx("my-key", GetExParams.getExParams());
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.get_ex("my-key", redis::Expiry::PERSIST)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GETRANGE
Source: https://upstash.com/docs/redis/commands/string/getrange

Use `GETRANGE` to read part of the string stored at a key.

`<start>` and `<end>` are zero-based byte offsets, both inclusive, and may be negative to count from the end, so `GETRANGE key 0 -1` returns the whole value. Offsets outside the string are clamped rather than treated as errors, and a missing key returns an empty string.

Since the range is applied on the server, this is how you read the head of a large value, or a fixed-width field inside one, without transferring the rest. `SUBSTR` is accepted as a deprecated alias with the same arguments and reply.

## Syntax

```redis
GETRANGE <key> <start> <end>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<start>` | Yes | No | Start byte offset; negative values count from the end. |
| `<end>` | Yes | No | End byte offset, inclusive; negative values count from the end. |

## Important points

* `SUBSTR` is accepted as a deprecated alias with the same arguments and reply. New code should use `GETRANGE`.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string |
| RESP3 | Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GETRANGE my-key 0 1
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const substring = await redis.getrange("key", 2, 4);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.getrange("my-key", "0", "1")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.getrange("my-key", "0", "1");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.getRange("my-key", 0, 1);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.getrange("my-key", "0", "1")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.GetRange(context.Background(), "my-key", 0, 1).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.getrange("my-key", 0, 1);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.getrange("my-key", 0, 1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# GETSET
Source: https://upstash.com/docs/redis/commands/string/getset

<Warning>
  Prefer [`SET`](/docs/redis/commands/string/set) with the `GET` argument in new code: `SET <key> <value> GET`.
</Warning>

Use `GETSET` to set a new value and get the previous one in a single atomic step.

The reply is the old value, or null when the key did not exist. Note that the key's time to live is discarded, so a key with an expiration becomes permanent when written this way.

The classic use is resetting a counter while reading it, where the atomicity guarantees nothing is counted twice. [`SET`](/docs/redis/commands/string/set) with the `GET` option does the same thing and can additionally set or keep an expiration in the same call.

## Syntax

```redis
GETSET <key> <value>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<value>` | Yes | No | String value to store in place of the current one. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string or Null bulk string or null array |
| RESP3 | Bulk string or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
GETSET my-key value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const oldValue = await redis.getset("key", newValue);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.getset("my-key", "value")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.getset("my-key", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.getSet("my-key", "value");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.getset("my-key", "value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.GetSet(context.Background(), "my-key", "value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.getSet("my-key", "value");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.getset("my-key", "value")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# INCR
Source: https://upstash.com/docs/redis/commands/string/incr

Use `INCR` to add one to the integer stored at a key and get the result.

A missing key counts as `0`, so the first call returns `1` and no initialization is needed. The value must be the string form of a 64-bit signed integer; anything else returns an error, as does an overflow.

The read, the addition, and the write are one atomic step, so concurrent callers each receive a distinct number and no increment is lost. That property is what makes it the basis for counters, id generators, and rate limiters, usually combined with [`EXPIRE`](/docs/redis/commands/generic/expire) to reset the count for each window.

## Syntax

```redis
INCR <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
INCR my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.set("key", 6);
await redis.incr("key");
// returns 7
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.incr("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.incr("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.incr("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.incr("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Incr(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.incr("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.incr("my-key", 1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# INCRBY
Source: https://upstash.com/docs/redis/commands/string/incrby

Use `INCRBY` to add an integer to the value stored at a key and get the result.

A missing key counts as `0`, the value must be the string form of a 64-bit signed integer, and an operation that would leave that range returns an error. A negative amount subtracts, which makes it interchangeable with [`DECRBY`](/docs/redis/commands/string/decrby).

Like [`INCR`](/docs/redis/commands/string/incr), the update is atomic, so it is safe for counters that several clients update at once, such as adding a batch size to a running total.

## Syntax

```redis
INCRBY <key> <increment>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<increment>` | Yes | No | Integer amount to add to the value. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
INCRBY my-key 1
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.set("key", 6);
await redis.incrby("key", 4);
// returns 10
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.incrby("my-key", 1)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.incrby("my-key", "1");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.incrBy("my-key", 1);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.incrby("my-key", 1)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.IncrBy(context.Background(), "my-key", 1).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.incrBy("my-key", 1);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.incr("my-key", 1)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# INCRBYFLOAT
Source: https://upstash.com/docs/redis/commands/string/incrbyfloat

Use `INCRBYFLOAT` to add a floating point number to the value stored at a key and get the result.

The stored value and the increment are parsed as double precision floats, and a missing key counts as `0`. A negative increment subtracts, and there is no separate decrement command. A value that is not a valid number returns an error.

The reply is the new value as a string, which client libraries usually decode into a native number. The result is stored in that same textual form, so repeated increments accumulate the usual binary floating point rounding error: for money and other exact quantities, count in integer units with [`INCRBY`](/docs/redis/commands/string/incrby) instead.

## Syntax

```redis
INCRBYFLOAT <key> <increment>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<increment>` | Yes | No | Amount to add to the value; may be a floating-point number. |

## Important points

* The value is always returned as a bulk string, in both RESP2 and RESP3. Client libraries commonly decode it to a language number.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Bulk string |
| RESP3 | Bulk string |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
INCRBYFLOAT my-key 1.5
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.set("key", 6);
await redis.incrbyfloat("key", 4,5);
// returns 10.5
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.incrbyfloat("my-key", 1.5)
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.incrbyfloat("my-key", "1.5");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.incrByFloat("my-key", 1.5);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.incrbyfloat("my-key", 1.5)
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.IncrByFloat(context.Background(), "my-key", 1.5).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.incrByFloat("my-key", 1.5);
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.incr("my-key", 1.5)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# MGET
Source: https://upstash.com/docs/redis/commands/string/mget

Use `MGET` to read the values of several keys in one call.

The reply holds one entry per key, in the order requested, so it can be zipped straight back onto your list of keys. Keys that do not exist, and keys holding a value that is not a string, come back as null rather than raising an error.

One `MGET` replaces N round trips of [`GET`](/docs/redis/commands/string/get), which is usually the difference that matters when loading a batch of cached objects.

## Syntax

```redis
MGET <key> [<key> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | Yes | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of bulk-string values or null values, one per key |
| RESP3 | Array of bulk-string values or null values, one per key |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
MGET key1 key2 key3
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

type MyType = {
    a: number;
    b: string;
}
const values = await redis.mget<MyType>("key1", "key2", "key3");
// values.length -> 3
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.mget("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.mget("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.mGet(["my-key"]);
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.mget("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.MGet(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.mget("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.mget(&["my-key"])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# MSET
Source: https://upstash.com/docs/redis/commands/string/mset

Use `MSET` to set several keys to their values in one atomic call.

All the keys are written together, with no other command running in between, so other clients never observe a state where only part of the batch has been applied. Existing values are overwritten and their expirations are discarded. The reply is always `OK`.

Use [`MSETNX`](/docs/redis/commands/string/msetnx) when the write should only happen if none of the keys exist, and set keys individually with [`SET`](/docs/redis/commands/string/set) when each needs its own expiration.

## Syntax

```redis
MSET <key> <value> [<key> <value> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key> <value>` | Yes | Yes | Key and the value to store in it. Repeat to set several keys in one call. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
MSET key1 1 key2 hello
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.mset({
    key1: 1,
    key2: "hello",
    key3: { a: 1, b: "hello" },
});
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.mset({"my-key": "value"})
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.mset("my-key", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.mSet({ "my-key": "value" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.mset({"my-key": "value"})
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.MSet(context.Background(), "my-key", "value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.mset("my-key", "value");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.mset(&[("my-key", "value")])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# MSETNX
Source: https://upstash.com/docs/redis/commands/string/msetnx

Use `MSETNX` to set several keys at once, but only if none of them already exists.

The command is all or nothing: if even a single key is present, nothing is written and the reply is `0`; otherwise every key is written and the reply is `1`. Since the check and the writes are one atomic step, it can be used to claim a whole group of related keys, so that a partially initialized state can never be observed.

## Syntax

```redis
MSETNX <key> <value> [<key> <value> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key> <value>` | Yes | Yes | Key and the value to store in it. Repeat to set several keys in one call. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: `1` if all keys were set, `0` if no key was set |
| RESP3 | Integer: `1` if all keys were set, `0` if no key was set |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
MSETNX key1 value1 key2 value2
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

redis.msetnx({
  "key1": "value1",
  "key2": "value2"
})
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.msetnx({"my-key": "value"})
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.msetnx("my-key", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.mSetNX({ "my-key": "value" });
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.msetnx({"my-key": "value"})
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.MSetNX(context.Background(), "my-key", "value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.msetnx("my-key", "value");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.mset_nx(&[("my-key", "value")])?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# String commands
Source: https://upstash.com/docs/redis/commands/string/overview

<CardGroup cols={2}>
<Card title="APPEND" href="/redis/commands/string/append">Append value to a string</Card>
<Card title="DECR" href="/redis/commands/string/decr">Decrement integer value by 1</Card>
<Card title="DECRBY" href="/redis/commands/string/decrby">Decrement integer by amount</Card>
<Card title="DELEX" href="/redis/commands/string/delex">Delete a string when a condition matches</Card>
<Card title="DIGEST" href="/redis/commands/string/digest">Get the digest of a string value</Card>
<Card title="GET" href="/redis/commands/string/get">Get string value</Card>
<Card title="GETDEL" href="/redis/commands/string/getdel">Get value and delete key</Card>
<Card title="GETEX" href="/redis/commands/string/getex">Get value and set expiration</Card>
<Card title="GETRANGE" href="/redis/commands/string/getrange">Get substring of string</Card>
<Card title="GETSET" href="/redis/commands/string/getset">Set value and return old value</Card>
<Card title="INCR" href="/redis/commands/string/incr">Increment integer value by 1</Card>
<Card title="INCRBY" href="/redis/commands/string/incrby">Increment integer by amount</Card>
<Card title="INCRBYFLOAT" href="/redis/commands/string/incrbyfloat">Increment float by amount</Card>
<Card title="MGET" href="/redis/commands/string/mget">Get values of multiple keys</Card>
<Card title="MSET" href="/redis/commands/string/mset">Set multiple keys at once</Card>
<Card title="MSETNX" href="/redis/commands/string/msetnx">Set multiple keys if none exist</Card>
<Card title="PSETEX" href="/redis/commands/string/psetex">Set value with ms expiration</Card>
<Card title="SET" href="/redis/commands/string/set">Set string value</Card>
<Card title="SETEX" href="/redis/commands/string/setex">Set value with expiration</Card>
<Card title="SETNX" href="/redis/commands/string/setnx">Set value if key doesn't exist</Card>
<Card title="SETRANGE" href="/redis/commands/string/setrange">Overwrite part of string</Card>
<Card title="STRLEN" href="/redis/commands/string/strlen">Get string length</Card>
</CardGroup>

# PSETEX
Source: https://upstash.com/docs/redis/commands/string/psetex

<Warning>
  Prefer [`SET`](/docs/redis/commands/string/set) with the `PX` argument in new code: `SET <key> <value> PX <milliseconds>`.
</Warning>

Use `PSETEX` to set a value together with a lifetime in milliseconds.

The value and the expiration are applied in the same atomic step, so the key never exists without its expiration, which is the point of the command: setting the value and then expiring it separately leaves a window where a crash makes the key permanent. A lifetime of zero or less returns an error.

It is the millisecond form of [`SETEX`](/docs/redis/commands/string/setex), and [`SET`](/docs/redis/commands/string/set) with the `PX` option is the more flexible way to write the same thing.

## Syntax

```redis
PSETEX <key> <milliseconds> <value>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<milliseconds>` | Yes | No | Lifetime in milliseconds. |
| `<value>` | Yes | No | String value to store. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
PSETEX my-key 1000 value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.psetex("my-key", 1000, "value");
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.psetex("my-key", 1000, "value")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.psetex("my-key", "1000", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.pSetEx("my-key", 1000, "value");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.psetex("my-key", 1000, "value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Set(context.Background(), "my-key", "value", time.Second).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.psetex("my-key", 1000, "value");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.pset_ex("my-key", "value", 1000)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SET
Source: https://upstash.com/docs/redis/commands/string/set

Use `SET` to store a string value at a key, replacing whatever was there before, whatever its type.

By default the value is written unconditionally and the key's previous time to live is discarded. `KEEPTTL` preserves it, while `EX`, `PX`, `EXAT`, and `PXAT` attach a new lifetime in seconds or milliseconds, or an absolute deadline as a Unix timestamp. Setting the value and its expiration in one command is what keeps a key from ever existing without one.

The condition decides whether the write happens at all. `NX` writes only when the key does not exist, which combined with an expiration is the standard way to acquire a lock, and `XX` writes only when it already exists, which refreshes a value without creating it. `IFEQ` and `IFNE` compare the current value with the one you supply, and `IFDEQ` and `IFDNE` compare its digest as returned by [`DIGEST`](/docs/redis/commands/string/digest), so a large value can be checked without sending it; these conditional forms are implemented by this Upstash deployment and give you compare-and-set semantics in a single command. `IFEQ` and `IFDEQ` require the key to exist, while `IFNE` and `IFDNE` also write when it does not.

`GET` makes the reply the previous value instead of `OK`, which is how you swap a value and read what it replaced in one atomic step. When a condition prevents the write, the reply is null.

## Syntax

```redis
SET <key> <value>
  [NX | XX | IFEQ <ifeq-value> | IFNE <ifne-value> |
    IFDEQ <ifdeq-digest> | IFDNE <ifdne-digest>]
  [GET]
  [EX <seconds> | PX <milliseconds> | EXAT <unix-time-seconds> |
    PXAT <unix-time-milliseconds> | KEEPTTL]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<value>` | Yes | No | String value to store. |
| `(NX \| XX \| IFEQ <ifeq-value> \| IFNE <ifne-value> \| IFDEQ <ifdeq-digest> \| IFDNE <ifdne-digest>)` | No | No | Choose one form: `NX` (only when the key does not exist); `XX` (only when it already exists); `IFEQ` (only when the current value equals `<ifeq-value>`); `IFNE` (only when it differs); `IFDEQ` (only when the current value's digest equals `<ifdeq-digest>`); `IFDNE` (only when it differs). |
| `GET` | No | No | Also return the previous value. |
| `(EX <seconds> \| PX <milliseconds> \| EXAT <unix-time-seconds> \| PXAT <unix-time-milliseconds> \| KEEPTTL)` | No | No | Choose one form: `EX` (set a lifetime in seconds); `PX` (set a lifetime in milliseconds); `EXAT` (expire at a Unix timestamp in seconds); `PXAT` (expire at a Unix timestamp in milliseconds); `KEEPTTL` (preserve the existing key lifetime). |

## Important points

* `NX` and `XX` are mutually exclusive.
* Choose at most one expiration form and at most one condition. `GET` changes a successful reply from `OK` to the previous value.
* `IFEQ`/`IFNE` compare the current value; `IFDEQ`/`IFDNE` compare its digest. These conditional forms are implemented by this Upstash deployment.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK`; with `GET`, the previous value as a bulk string, or Null bulk string or null array when the key did not exist or a condition failed |
| RESP3 | Simple string `OK`; with `GET`, the previous value as a bulk string, or Null when the key did not exist or a condition failed |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SET my-key value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.set("my-key", {my: "value"});
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.set("my-key", "value")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.set("my-key", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.set("my-key", "value");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.set("my-key", "value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.Set(context.Background(), "my-key", "value", 0).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.set("my-key", "value");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.set("my-key", "value")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SETEX
Source: https://upstash.com/docs/redis/commands/string/setex

<Warning>
  Prefer [`SET`](/docs/redis/commands/string/set) with the `EX` argument in new code: `SET <key> <value> EX <seconds>`.
</Warning>

Use `SETEX` to set a value together with a lifetime in seconds.

The value and the expiration are applied atomically, so the key never exists without an expiration; writing the value and expiring it in two commands leaves a window in which a failure makes the key permanent. A lifetime of zero or less returns an error.

Use [`PSETEX`](/docs/redis/commands/string/psetex) for millisecond precision. [`SET`](/docs/redis/commands/string/set) with `EX` or `PX` does the same thing and additionally supports conditions and reading the previous value.

## Syntax

```redis
SETEX <key> <seconds> <value>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<seconds>` | Yes | No | Lifetime in seconds. |
| `<value>` | Yes | No | String value to store. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SETEX my-key 1000 value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.setex("my-key", 1000, "value");
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.setex("my-key", 1000, "value")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.setex("my-key", "1000", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.setEx("my-key", 1000, "value");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.setex("my-key", 1000, "value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SetEx(context.Background(), "my-key", "value", 1000*time.Second).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.setex("my-key", 1000, "value");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.set_ex("my-key", "value", 1000)?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SETNX
Source: https://upstash.com/docs/redis/commands/string/setnx

<Warning>
  Prefer [`SET`](/docs/redis/commands/string/set) with the `NX` argument in new code: `SET <key> <value> NX`.
</Warning>

Use `SETNX` to set a value only if the key does not already exist.

The reply is `1` when the key was created and `0` when it was already there and nothing was written. The check and the write are atomic, so exactly one of several racing clients succeeds.

That makes it the classic lock primitive, but on its own it is an incomplete one: a client that fails after acquiring leaves the key behind forever. Prefer `SET key value NX EX <ttl>`, which claims the key and attaches an expiration in the same step, and release the lock with [`DELEX`](/docs/redis/commands/string/delex) so you only delete it while you still own it.

## Syntax

```redis
SETNX <key> <value>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<value>` | Yes | No | String value to store. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer: `1` if the key was set, `0` if it already exists |
| RESP3 | Integer: `1` if the key was set, `0` if it already exists |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SETNX my-key value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const result = await redis.setnx("my-key", "value");
console.log(result);
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.setnx("my-key", "value")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.setnx("my-key", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.setNX("my-key", "value");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.setnx("my-key", "value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SetNX(context.Background(), "my-key", "value", 0).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.setnx("my-key", "value");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.set_nx("my-key", "value")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# SETRANGE
Source: https://upstash.com/docs/redis/commands/string/setrange

Use `SETRANGE` to overwrite part of the string stored at a key, starting at a byte offset.

The key is created when it does not exist, and if the offset lies past the current end of the value the string is first padded with zero bytes, so writing at a large offset allocates everything up to it. The reply is the length of the string after the write.

Together with [`GETRANGE`](/docs/redis/commands/string/getrange) this makes it possible to treat a string as a fixed-layout record and update one field in place, without reading and rewriting the whole value.

## Syntax

```redis
SETRANGE <key> <offset> <value>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |
| `<offset>` | Yes | No | Zero-based byte offset to write at. The value is zero-padded if the key is shorter. |
| `<value>` | Yes | No | String written starting at the offset. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
SETRANGE my-key 0 value
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.set("key", "helloworld")
const length = await redis.setrange("key", 5, "redis");
console.log(length); // 10

// The value of "key" is now "helloredis"
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.setrange("my-key", 0, "value")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.setrange("my-key", "0", "value");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.setRange("my-key", 0, "value");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.setrange("my-key", 0, "value")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.SetRange(context.Background(), "my-key", 0, "value").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.setrange("my-key", 0, "value");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.setrange("my-key", 0, "value")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# STRLEN
Source: https://upstash.com/docs/redis/commands/string/strlen

Use `STRLEN` to get the length in bytes of the string stored at a key.

The reply is `0` when the key does not exist, and an error when the value is not a string. Since the value itself is never transferred, it is the cheap way to check how large a value is, for instance before deciding whether to fetch it or to read only part of it with [`GETRANGE`](/docs/redis/commands/string/getrange). The count is in bytes, so multi-byte characters count more than once.

## Syntax

```redis
STRLEN <key>
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | No | Redis key targeted by the command. |

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Integer |
| RESP3 | Integer |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
STRLEN my-key
```

</Accordion>

<Accordion title="@upstash/redis" icon="node-js" iconType="brands">

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.set("key", "helloworld")
const length = await redis.strlen("key");
console.log(length); // 10
```

</Accordion>

<Accordion title="upstash_redis" icon="python" iconType="brands">

```python
from upstash_redis import Redis

redis = Redis.from_env()
result = redis.strlen("my-key")
print(result)
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);
const result = await redis.strlen("my-key");
console.log(result);
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL })
  .on("error", console.error)
  .connect();
const result = await client.strLen("my-key");
console.log(result);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
result = client.strlen("my-key")
print(result)
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/redis/go-redis/v9"
)

func main() {
    opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
    if err != nil {
        panic(err)
    }
    client := redis.NewClient(opts)
    result, err := client.StrLen(context.Background(), "my-key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  Object result = jedis.strlen("my-key");
  System.out.println(result);
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
use redis::TypedCommands;

fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let result = connection.strlen("my-key")?;
    println!("{result:?}");
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# DISCARD
Source: https://upstash.com/docs/redis/commands/transactions/discard

Use `DISCARD` to throw away the commands queued since [`MULTI`](/docs/redis/commands/transactions/multi) and leave the transaction without running anything.

It also clears every key watched with [`WATCH`](/docs/redis/commands/transactions/watch), so the connection ends up in its normal state. This is what you call when the data you read after `WATCH` turns out to make the queued update unnecessary or invalid.

The raw command is TCP-only. Over HTTP, use the transaction or pipeline API of an Upstash SDK instead of sending this command directly.

## Syntax

```redis
DISCARD
```

## Arguments

This command takes no arguments.

## Important points

* The raw command is TCP-only. For HTTP, use an Upstash SDK transaction API rather than sending this command directly.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
MULTI
SET balance 100
DISCARD
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const client = new Redis(process.env.REDIS_URL!);
await client.multi({ pipeline: false });
await client.set("balance", "100");
await client.discard();
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL }).connect();
await client.sendCommand(["MULTI"]);
await client.sendCommand(["SET", "balance", "100"]);
await client.sendCommand(["DISCARD"]);
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
with client.pipeline(transaction=True) as pipe:
    pipe.set("balance", "100")
    pipe.reset()
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
ctx := context.Background()
opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
if err != nil { panic(err) }
client := redis.NewClient(opts)

pipe := client.TxPipeline()
pipe.Set(ctx, "balance", "100", 0)
pipe.Discard()
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")));
     Transaction transaction = jedis.multi()) {
  transaction.set("balance", "100");
  transaction.discard();
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let mut transaction = redis::pipe();
    transaction.atomic().set("balance", "100");
    transaction.clear();
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# EXEC
Source: https://upstash.com/docs/redis/commands/transactions/exec

Use `EXEC` to run the commands queued since [`MULTI`](/docs/redis/commands/transactions/multi).

They execute one after another with no other client's command in between, and the reply is an array holding one result per queued command, in order. Errors raised by individual commands appear in that array rather than stopping the rest, so check each entry.

If any key watched with [`WATCH`](/docs/redis/commands/transactions/watch) was modified after it was watched, the transaction is not executed at all and the reply is null, which is the signal to read the data again and retry. Watches are cleared afterwards either way.

The raw command is TCP-only. Over HTTP, use the transaction or pipeline API of an Upstash SDK instead of sending this command directly.

## Syntax

```redis
EXEC
```

## Arguments

This command takes no arguments.

## Important points

* The raw command is TCP-only. For HTTP, use an Upstash SDK transaction API rather than sending this command directly.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Array of command replies or Null bulk string or null array |
| RESP3 | Array of command replies or Null |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
MULTI
SET balance 100
EXEC
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const client = new Redis(process.env.REDIS_URL!);
const result = await client.multi().set("balance", "100").exec();
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL }).connect();
const result = await client.multi().set("balance", "100").exec();
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
with client.pipeline(transaction=True) as pipe:
    result = pipe.set("balance", "100").execute()
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
ctx := context.Background()
opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
if err != nil { panic(err) }
client := redis.NewClient(opts)

pipe := client.TxPipeline()
pipe.Set(ctx, "balance", "100", 0)
result, err := pipe.Exec(ctx)
if err != nil { panic(err) }
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")));
     Transaction transaction = jedis.multi()) {
  transaction.set("balance", "100");
  Object result = transaction.exec();
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    redis::pipe()
        .atomic()
        .set("balance", "100")
        .ignore()
        .query::<()>(&mut connection)?;
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# MULTI
Source: https://upstash.com/docs/redis/commands/transactions/multi

Use `MULTI` to start a transaction.

Commands sent afterwards are not executed but queued, each answered with `QUEUED`, until [`EXEC`](/docs/redis/commands/transactions/exec) runs them all in order with nothing else in between, or [`DISCARD`](/docs/redis/commands/transactions/discard) throws them away.

A Redis transaction is atomic in the sense that no other client sees a partial result, but it is not a rollback mechanism: a command that fails at queue time, such as one with a syntax error, aborts the whole transaction, while a command that fails at execution time, such as one applied to the wrong type, leaves the commands around it applied. Combine it with [`WATCH`](/docs/redis/commands/transactions/watch) when the transaction depends on values you read beforehand.

The raw command is TCP-only. Over HTTP, use the transaction or pipeline API of an Upstash SDK instead of sending this command directly.

## Syntax

```redis
MULTI
```

## Arguments

This command takes no arguments.

## Important points

* The raw command is TCP-only. For HTTP, use an Upstash SDK transaction API rather than sending this command directly.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
MULTI
SET balance 100
EXEC
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const client = new Redis(process.env.REDIS_URL!);
const result = await client.multi().set("balance", "100").exec();
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL }).connect();
const result = await client.multi().set("balance", "100").exec();
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
with client.pipeline(transaction=True) as pipe:
    result = pipe.set("balance", "100").execute()
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
ctx := context.Background()
opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
if err != nil { panic(err) }
client := redis.NewClient(opts)

pipe := client.TxPipeline()
pipe.Set(ctx, "balance", "100", 0)
result, err := pipe.Exec(ctx)
if err != nil { panic(err) }
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")));
     Transaction transaction = jedis.multi()) {
  transaction.set("balance", "100");
  Object result = transaction.exec();
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    redis::pipe()
        .atomic()
        .set("balance", "100")
        .ignore()
        .query::<()>(&mut connection)?;
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# Transactions commands
Source: https://upstash.com/docs/redis/commands/transactions/overview

<CardGroup cols={2}>
<Card title="DISCARD" href="/redis/commands/transactions/discard">Discard queued commands</Card>
<Card title="EXEC" href="/redis/commands/transactions/exec">Execute queued commands</Card>
<Card title="MULTI" href="/redis/commands/transactions/multi">Start a transaction</Card>
<Card title="UNWATCH" href="/redis/commands/transactions/unwatch">Unwatch all keys</Card>
<Card title="WATCH" href="/redis/commands/transactions/watch">Watch keys for changes</Card>
</CardGroup>

# UNWATCH
Source: https://upstash.com/docs/redis/commands/transactions/unwatch

Use `UNWATCH` to forget all the keys watched with [`WATCH`](/docs/redis/commands/transactions/watch) on this connection.

Afterwards the next [`EXEC`](/docs/redis/commands/transactions/exec) is no longer conditional on those keys. It is the way to abandon an optimistic locking attempt when, after reading the watched data, you decide not to run a transaction at all, so a later unrelated transaction is not aborted by a change to keys you no longer care about. [`EXEC`](/docs/redis/commands/transactions/exec) and [`DISCARD`](/docs/redis/commands/transactions/discard) clear watches on their own.

The raw command is TCP-only. Over HTTP, use the transaction or pipeline API of an Upstash SDK instead of sending this command directly.

## Syntax

```redis
UNWATCH
```

## Arguments

This command takes no arguments.

## Important points

* The raw command is TCP-only. For HTTP, use an Upstash SDK transaction API rather than sending this command directly.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
UNWATCH
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const client = new Redis(process.env.REDIS_URL!);
await client.watch("balance");
await client.unwatch();
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL }).connect();
await client.watch("balance");
await client.unwatch();
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
with client.pipeline() as pipe:
    pipe.watch("balance")
    pipe.unwatch()
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
ctx := context.Background()
opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
if err != nil { panic(err) }
client := redis.NewClient(opts)

err = client.Watch(ctx, func(tx *redis.Tx) error {
    return tx.Unwatch(ctx).Err()
}, "balance")
if err != nil { panic(err) }
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  jedis.watch("balance");
  jedis.unwatch();
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    redis::transaction(&mut connection, &["balance"], |_con, _pipe| {
        Ok(Some(()))
    })?;
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# WATCH
Source: https://upstash.com/docs/redis/commands/transactions/watch

Use `WATCH` to mark keys whose modification should cancel the next transaction, which gives you optimistic locking.

If any watched key is changed by another client between the `WATCH` and the [`EXEC`](/docs/redis/commands/transactions/exec), the transaction is not executed and `EXEC` replies with null instead. Nothing is locked in the meantime: other clients keep working normally, and the conflict is detected rather than prevented.

The usual loop is: watch the keys, read them, decide what to write, open [`MULTI`](/docs/redis/commands/transactions/multi), queue the writes, and call `EXEC`, retrying from the top when the reply is null. That is how a read-modify-write cycle stays correct without holding a lock. All watches are cleared by `EXEC` and [`DISCARD`](/docs/redis/commands/transactions/discard).

The raw command is TCP-only. Over HTTP, use the transaction or pipeline API of an Upstash SDK instead of sending this command directly.

## Syntax

```redis
WATCH <key> [<key> ...]
```

## Arguments

| Argument | Required | Repeatable | Description |
| --- | --- | --- | --- |
| `<key>` | Yes | Yes | Redis key targeted by the command. |

## Important points

* The raw command is TCP-only. For HTTP, use an Upstash SDK transaction API rather than sending this command directly.

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| --- | --- |
| RESP2 | Simple string `OK` |
| RESP3 | Simple string `OK` |

<Note>
  Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
</Note>

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

<AccordionGroup>

<Accordion title="Redis CLI" icon="terminal">

```bash
WATCH balance
```

</Accordion>

<Accordion title="ioredis" icon="node-js" iconType="brands">

```ts
import Redis from "ioredis";

const client = new Redis(process.env.REDIS_URL!);
await client.watch("balance");
const result = await client.multi().set("balance", "100").exec();
```

</Accordion>

<Accordion title="node-redis" icon="node-js" iconType="brands">

```ts
import { createClient } from "redis";

const client = await createClient({ url: process.env.REDIS_URL }).connect();
await client.watch("balance");
const result = await client.multi().set("balance", "100").exec();
```

</Accordion>

<Accordion title="redis-py" icon="python" iconType="brands">

```python
import os
import redis

client = redis.from_url(os.environ["REDIS_URL"])
with client.pipeline() as pipe:
    pipe.watch("balance")
    pipe.multi()
    pipe.set("balance", "100")
    result = pipe.execute()
```

</Accordion>

<Accordion title="go-redis" icon="golang" iconType="brands">

```go
ctx := context.Background()
opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
if err != nil { panic(err) }
client := redis.NewClient(opts)

err = client.Watch(ctx, func(tx *redis.Tx) error {
    _, err := tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
        pipe.Set(ctx, "balance", "100", 0)
        return nil
    })
    return err
}, "balance")
if err != nil { panic(err) }
```

</Accordion>

<Accordion title="jedis" icon="java" iconType="brands">

```java
import java.net.URI;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
  jedis.watch("balance");
  try (Transaction transaction = jedis.multi()) {
    transaction.set("balance", "100");
    Object result = transaction.exec();
  }
}
```

</Accordion>

<Accordion title="redis-rs" icon="rust" iconType="brands">

```rust
fn main() -> redis::RedisResult<()> {
    let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
    let client = redis::Client::open(url)?;
    let mut connection = client.get_connection()?;

    let _: () = redis::transaction(&mut connection, &["balance"], |con, pipe| {
        pipe.set("balance", "100").ignore().query::<Option<()>>(con)
    })?;
    Ok(())
}
```

</Accordion>

</AccordionGroup>

# Auto Upgrade
Source: https://upstash.com/docs/redis/features/auto-upgrade

By default, Upstash will apply usage limits based on your current plan. When you reach these limits, behavior depends on the specific limit type - bandwidth limits will throttle your traffic, while storage limits will reject new write operations. However, Upstash offers an Auto Upgrade feature that automatically upgrades your database to the next higher plan when you reach your usage limits, ensuring uninterrupted service.

Auto Upgrade is particularly useful for applications with fluctuating or growing workloads, as it prevents service disruptions during high-traffic periods or when your data storage needs expand beyond your current plan. This feature allows your database to automatically scale with your application's demands without requiring manual intervention.

## How Auto Upgrade Works

When enabled:

* For **bandwidth limits**: Instead of throttling your traffic when you reach the bandwidth limit, your database will automatically upgrade to the next plan to accommodate the increased traffic.
* For **storage limits**:
    * **When eviction is off**: Instead of rejecting write operations when you reach maximum data size, your database will upgrade to a plan with larger storage capacity.
    * **When eviction is on**: Your data will be evicted and operations will resume. Auto Upgrade will not be triggered and system will rely on eviction mechanism in this case.

## Managing Auto Upgrade

* You can enable Auto Upgrade by checking the Auto Upgrade checkbox while creating a new database:

    <img width="%50" />

* Or for an existing database by clicking Enable in the Configuration/Auto Upgrade box in the database details page:
    <img width="600" height="300" />

# Backup/Restore
Source: https://upstash.com/docs/redis/features/backup

You can create backups of your Redis database and restore them when needed. Backups allow you to preserve your data and recover it to any database in your account or team.

## Creating a Backup

<Info>
  During a backup operation, certain administrative operations will be temporarily unavailable: Backup operations, database config changes, plan and region setup, transferring database.
  Regular Redis commands (GET, SET, etc.) are not blocked and continue to work normally.
</Info>

There are two ways to create a backup of your database:

### Create an Immediate Backup

To create a backup right now:

* Go to the database details page and navigate to the `Backups` tab
* Click on the `Backup & Export` button
* Choose `Backup`

  <img width="800" />

Backup process will start and will appear in the backups table below.

### Schedule Periodic Backups

To automatically create backups on a regular schedule:

* Go to the database details page and navigate to the `Backups` tab
* Click the switch next to `Daily Backup` to enable daily backup or click on `Daily Backup` text itself to select how long the backup is to be stored (1 or 3 days)

  <img width="800" />

With daily backups enabled, your database will be automatically backed up every day.

### Managing Backups

All created backups are displayed in the backups table in the `Backups` tab. From this table, you can:

* View backup details (name, creation date, size)
* Restore your database from any backup
* Delete backups you no longer need

## Restoring from Backup

<Warning>
  All existing data in the target database will be deleted before the restore operation begins.
</Warning>

You can restore your database from any backup in your account or team.

### Restore from the Backups Table

To restore from a backup of the current database:

* Go to the database details page and navigate to the `Backups` tab
* Find the backup you want to restore in the backups table
* Click on the `Restore` button next to the backup
* Confirm that you are deleting existing data and want to proceed with the restore

  <img width="800" />

### Restore from Any Database Backup

To restore from a backup created from any database in your account or team:

* Go to the database details page and navigate to the `Backups` tab
* Click on the `Restore...` button
* Select the source database (the database from which the backup was created)
* Select the backup you want to restore
* Click on `Start Restore`

  <img width="700" />

### Restore from the Redis List Page

You can also restore databases directly from the Redis list page. This method is explained in detail in the [Import/Export documentation](/docs/redis/howto/importexport).

# Consistency
Source: https://upstash.com/docs/redis/features/consistency

Upstash utilizes a leader-based replication mechanism. Under this mechanism,
each key is assigned to a leader replica, which is responsible for handling
write operations on that key. The remaining replicas serve as backups to the
leader. When a write operation is performed on a key, it is initially processed
by the leader replica and then asynchronously propagated to the backup replicas.
This ensures that data consistency is maintained across the replicas. Reads can
be performed from any replica.

Each replica employs a failure detector to track liveness of the leader replica.
When the leader replica fails for a reason, remaining replicas start a new
leader election round and elect a new leader. This is the only unavailability
window for the cluster where _write_ your requests can be blocked for a short
period of time. Also in case of cluster wide failures like network partitioning
(split brain); periodically running anti entropy jobs resolve the conflicts
using `Last-Writer-Wins` algorithm and converge the replicas to the same state.

This model gives a better write consistency and read scalability but can provide
only **Eventual Consistency**. Additionally you can achieve **Causal
Consistency** (`Read-Your-Writes`, `Monotonic-Reads`, `Monotonic-Writes` and
`Writes-Follow-Reads` guarantees) for a single Redis connection. (A TCP
connection forms a session between client and server).

Checkout [Read Your Writes](/docs/redis/howto/readyourwrites) for more details on how to achieve RYW consistency.

Checkout [Replication](/docs/redis/features/replication) for more details on Replication mechanism.

<Note>
  Previously, Upstash supported `Strong Consistency` mode for the single region
  databases. We decided to deprecate this feature because its effect on latency
  started to conflict with the performance expectations of Redis use cases. Also
  we are gradually moving to **CRDT** based Redis data structures, which will
  provide `Strong Eventual Consistency`.
</Note>

# Credential Protection
Source: https://upstash.com/docs/redis/features/credential-protection

Enabling Credential Protection ensures your database credentials are never stored within Upstash infrastructure. This enhances security by making credentials accessible only once—at the moment they are generated.

<Note>
  Credential Protection is a [Production
  Pack](/docs/redis/overall/enterprise#prod-pack-features)
  feature.
</Note>

## How It Works

When enabled:

* Redis database credentials are no longer stored in Upstash infrastructure
* Credentials are displayed only once during enablement - save them immediately
* Console features requiring database access are disabled (CLI, Data Browser, Monitor, ACL)

## Managing Credential Protection

1. Go to database details page → Configuration section
2. Toggle **Protect Credentials** switch:

  <img />

3. Save the credentials shown in the modal:

  <img />

<Warning>
  Disabling this feature will permanently revoke current credentials and
  generate new ones, potentially breaking applications using those credentials.
</Warning>

## What If You Lose Your Credentials

**Reset Credentials**: This function remains available and, when credential protection is enabled, will generate new protected credentials.
Navigate to the settings tab of your database and click **Reset Credentials**. You will be prompted to save the new credentials.

  <img />

# Durable Storage
Source: https://upstash.com/docs/redis/features/durability

In Upstash, persistence is always enabled, setting it apart from other Redis
offerings. Every write operation is consistently stored in both memory and the
block storage provided by cloud providers, such as AWS's EBS. This dual storage
approach ensures data durability. Read operations are optimized to first check
if the data exists in memory, facilitating faster access. If the data is not in
memory, it is retrieved from disk. This combination of memory and disk storage
in Upstash guarantees reliable data access and maintains data integrity, even
during system restarts or failures.

### Multi Tier Storage

Upstash keeps your data both in memory and disk. This design provides:

* Data safety with persistent storage
* Low latency with in memory access
* Price flexibility by using memory only for active data

In Upstash, an entry in memory is evicted if it remains idle, meaning it has not
been accessed for an extended period. It's important to note that eviction does
not result in data loss since the entry is still stored in the block storage.
When a read operation occurs for an evicted entry, it is efficiently reloaded
from the block storage back into memory, ensuring fast access to the data. This
eviction mechanism in Upstash optimizes memory usage by prioritizing frequently
accessed data while maintaining the ability to retrieve less frequently accessed
data when needed.

  <img width="600" />

<Card
  title="Can I use Upstash as a database?"
  icon="lightbulb"
  iconType="duotone"
  color="#34D399"
>
  Definitely, yes. Some users are worried that Redis data will be lost when a
  server crashes. This is not the case for Upstash thanks to Durable Storage.
  Data is reloaded to memory from block storage in case of a server crash.
  Moreover, except for the free tier, all paid tier databases provide extra redundancy by replicating data to multiple instances.
</Card>

# Eviction
Source: https://upstash.com/docs/redis/features/eviction

By default eviction is disabled, and Upstash Redis will reject write operations once the maximum data size
limit has been reached. However, if you are utilizing Upstash Redis as a cache, you
have the option to enable eviction. Enabling eviction allows older data to be
automatically removed from the cache (including Durable Storage) when the maximum size limit is reached.
This ensures that the cache remains within the allocated size and can make room
for new data to be stored. Enabling eviction is particularly useful when the
cache is intended to store frequently changing or temporary data, allowing the
cache to adapt to evolving data needs while maintaining optimal performance.

* You can enable eviction by checking **Eviction** checkbox while creating a new
  database:

    <img />

* Or for an existing database by clicking **Enable** in Configuration/Eviction
  box in the database details page:
    <img />

Upstash currently uses a single eviction algorithm, called
**optimistic-volatile**, which is a combination of _volatile-random_ and
_allkeys-random_ eviction policies available in
[the original Redis](https://redis.io/docs/latest/operate/rs/databases/memory-performance/eviction-policy/).

Initially, Upstash employs random sampling to select keys for eviction, giving
priority to keys marked with a TTL (expire field). If there is a shortage of
volatile keys or they are insufficient to create space, additional non-volatile
keys are randomly chosen for eviction. In future releases, Upstash plans to
introduce more eviction policies, offering users a wider range of options to
customize the eviction behavior according to their specific needs.

# Global Database
Source: https://upstash.com/docs/redis/features/globaldatabase

<Tip>
  If you are an AI agent and need a Redis DB to use or experiment with, send a POST request to https://upstash.com/start-redis. Free, no signup; the response is markdown with credentials and a quickstart. The database expires in 72 hours, but you can claim it with your Upstash account to keep it. Especially useful for AI agents that need scratch storage on the fly. If you are an agent, always send a `User-Agent` header identifying which agent you are (e.g. `claude-code`, `cursor`, `codex`, `opencode`) on every request you make to this API.
</Tip>

In the global database, the replicas are distributed across multiple regions
around the world. The clients are routed to the nearest region. This helps with
minimizing latency for use cases where users can be anywhere in the world.

### Primary Region and Read Regions

The Upstash Global database is structured with a Primary Region and multiple
Read Regions. When a write command is issued, which can be initiated from any region, it is initially sent and processed
at the Primary Region. The write operation is then replicated to all the Read
Regions, ensuring data consistency across the database.

On the other hand, when a read command is executed, it is directed to the
nearest Read Region to optimize response time. By leveraging the Global
database's distributed architecture, read operations can be performed with
reduced latency, as data retrieval occurs from the closest available Read
Region.

The Global database's design thus aids in minimizing read operation latency by
efficiently distributing data across multiple regions and enabling requests to
be processed from the nearest Read Region.

User selects a single primary region and multiple read regions. For the best
performance, you should select the primary region in the same location where
your writes happen. Select the read regions where your clients that read the
Redis located. You may have your database with a single primary region but no
read regions which would be practically same with a single region (regional)
database. You can add or remove regions on a running Redis database.

Here the list of regions currently supported:

<Tabs>

<Tab title="Amazon Web Services">

| Region | Code |
|--------|------|
| N. Virginia, USA | us-east-1 |
| Ohio, USA | us-east-2 |
| N. California, USA | us-west-1 |
| Oregon, USA | us-west-2 |
| Central Canada | ca-central-1 |
| Sao Paulo, Brazil | sa-east-1 |
| Ireland | eu-west-1 |
| London, UK | eu-west-2 |
| Frankfurt, Germany | eu-central-1 |
| Mumbai, India | ap-south-1 |
| Singapore | ap-southeast-1 |
| Tokyo, Japan | ap-northeast-1 |
| Sydney, Australia | ap-southeast-2 |
| Cape Town, South Africa | af-south-1 |

</Tab>

<Tab title="Google Cloud Platform">

| Region | Code |
|--------|------|
| Iowa, USA | us-central1 |
| Virginia, USA | us-east4 |
| Belgium | europe-west1 |
| Tokyo, Japan | asia-northeast1 |

</Tab>

</Tabs>

  <img width="520" />

In our internal tests, we see the following latencies (99th percentile):

* Read latency from the same region &lt;1ms
* Write latency from the same region &lt;5ms
* Read/write latency from the same continent &lt;50ms

  <img width="1000" />

### Architecture

In the multi region architecture, each key is owned by a primary replica which
is located at the region that you choose as primary region. Read replicas become
the backups of the primary for the related keys. The primary replica processes
the writes, then propagates them to the read replicas. Read requests are
processed by all replicas, this means you can read a value from any of the
replicas. This model gives a better write consistency and read scalability.

Each replica employs a failure detector to track the liveness of the primary
replica. When the primary replica fails for a reason, read replicas start a new
leader election round and elect a new leader (primary). This is the only
unavailability window for the cluster where your requests can be blocked for a
short period of time.

<Note>
  Global Database is designed to optimize the latency of READ operations. It may
  not be a good choice if your use case is WRITE heavy.
</Note>

### Use Cases

* **Edge functions:** Edge computing (Cloudflare workers, Fastly Compute) is
  becoming a popular way of building globally fast applications. But there are
  limited data solutions accessible from edge functions. Upstash Global Database
  is accessible from Edge functions with the REST API. Low latency from all edge
  locations makes it a perfect solution for Edge functions

* Multi region serverless architectures: You can run your AWS Lambda function in
  multiple regions to lower global latency. Vercel/Netlify functions can be run
  in different regions. Upstash Global database provides low latency data
  wherever your serverless functions are.

* Web/mobile use cases where you need low latency globally. Thanks to the read
  only REST API, you can access Redis from your web/mobile application directly.
  In such a case, Global Database will help to lower the latency as you can
  expect the clients from anywhere.

### High Availability and Disaster Recovery

Although the main motivation behind the Global Database is to provide low
latency; it also makes your database resilient to region wide failures. When a
region is not available, your requests are routed to another region; so your
database remains available.

### Consistency

Global Database is an eventually consistent database. The write request returns
after the primary replica processes the operation. Write operation is replicated
to read replicas asynchronously. Read requests can be served by any replica,
which gives better horizontal scalability but also means a read request may
return a stale value while a write operation for the same key is being
propagated to read replicas.

In case of cluster wide failures like network partitioning (split brain);
periodically running anti entropy jobs resolve the conflicts using LWW
algorithms and converge the replicas to the same state.

### Upgrade from Regional to Global

Currently, we do not support auto-upgrade from regional to global database. You
can export data from your old database and import into the global database.

### Pricing

Global Database charges \$0.2 per 100K commands. The write commands are replicated to all read regions in addition to primary region so the replications are counted as commands. For example, if you have 1 primary 1 read region, 100K writes will cost \$0.4 (\$0.2 x 2). You can use Global Database in the free tier too. Free usage is limited with max one read region.

# Key-Based Locking
Source: https://upstash.com/docs/redis/features/key-locking

Upstash Redis databases use locks to keep commands isolated while allowing
independent keys to be processed in parallel. The engine automatically locks
the keys used by the command. Commands that operate on different keys can run
concurrently, subject to the parallelism available to your database.

Key-based locking is transparent to clients. You do not need to change regular
Redis commands to use it.

## How It Works

* Single-key commands (for example `GET`, `SET`, `INCR`, `HSET`) acquire a
  lock on just that key.
* Multi-key commands acquire locks on every key they reference, in a
  deterministic order to avoid deadlocks.
* Read-only commands (for example `GET`, `HGET`, `LRANGE`) take a shared read
  lock, so multiple readers on the same key run concurrently. Read
  locks block writers on that key until they complete.
* Commands that need a database-wide operation, such as `FLUSHDB` and
  `FLUSHALL`, take the global lock and can reduce concurrency while they run.

## Transactions

Transactions (`MULTI`/`EXEC`) use key-based locking at `EXEC` time. While
commands are queued, Upstash collects the keys referenced by the transaction.
When `EXEC` runs, the engine takes an exclusive write lock for the union of
those keys and executes the queued commands atomically.

Transactions that touch disjoint key sets can run concurrently. Transactions
that share any key block each other until one transaction finishes.

```redis
MULTI
SET user:42:name "Ada"
INCR user:42:version
EXEC
```

In this example, `EXEC` locks `user:42:name` and `user:42:version` for the
duration of the transaction.

If a queued command requires a database-wide lock, the whole transaction uses
the global lock. This includes commands such as `FLUSHDB` and `FLUSHALL`.

Lua scripts queued inside a transaction always execute under the global lock,
even if the script declares [`allow-key-locking`](#lua-scripts). If you want
script-level key locking, run the script directly with
[`EVAL`](/docs/redis/commands/scripting/eval) /
[`EVALSHA`](/docs/redis/commands/scripting/evalsha) outside of a transaction.

## Lua Scripts

Lua scripts ([`EVAL`](/docs/redis/commands/scripting/eval),
[`EVALSHA`](/docs/redis/commands/scripting/evalsha),
[`EVAL_RO`](/docs/redis/commands/scripting/eval-ro),
[`EVALSHA_RO`](/docs/redis/commands/scripting/evalsha-ro)) default to the global
lock because the engine cannot know in advance which keys the script will use.
To opt into key-based locking, add the `allow-key-locking` flag to the script's
shebang line:

```lua
#!lua flags=allow-key-locking

redis.call('INCR', KEYS[1])
redis.call('INCRBY', KEYS[2], ARGV[1])
return "OK"
```

When the flag is set, Upstash locks only the keys passed through the `KEYS`
array when the script is invoked. Other commands and scripts that touch disjoint
keys can run in parallel.

### Rules for `allow-key-locking`

* **Every key passed to `redis.call` must appear in `KEYS`.** You may compute
  the key value inside the script (for example by concatenating parts of
  `ARGV`), but the final string must exactly match one of the entries declared
  in the `KEYS` array. Otherwise the engine rejects the command:

  ```
  ERR Dynamic keys are not allowed in Lua scripts when 'allow-key-locking' flag is set. Key was: <key>
  ```

  In practice, this means you should pass fully resolved keys through `KEYS`
  rather than reconstructing them from `ARGV` inside the script. This is worth
  doing even for scripts that run under the global lock, because an undeclared
  key can force a disk read while the lock is held. See
  [Dynamic Keys and Latency](#dynamic-keys-and-latency).

* **Database-wide writes are not allowed.** Commands that require database-wide
  exclusive access, such as `FLUSHDB` and `FLUSHALL`, cannot be called from a
  script with `allow-key-locking`. Run those scripts without the flag so the
  engine can use the global lock.

Read-only script variants and scripts with the `no-writes` flag also need
`allow-key-locking` if you want them to use per-key read locks. Without it, they
run under the global lock. To use both flags in a Lua script, separate them with
a comma:

```lua
#!lua flags=no-writes,allow-key-locking
```

### When to use it

Enable `allow-key-locking` for short scripts that operate on a small, known
set of keys and are called frequently enough that the global lock becomes a
bottleneck (for example counters, rate limiters, or per-user state
transitions). For scripts that must scan or mutate many keys at once, leave
the flag off so the engine uses the global lock.

### Example: Key-Locked Counter

```lua
#!lua flags=allow-key-locking

local current = tonumber(redis.call('GET', KEYS[1]) or "0")
if current >= tonumber(ARGV[1]) then
  return 0
end
redis.call('INCR', KEYS[1])
return 1
```

Invoked with:

```
EVAL "<script>" 1 user:42:quota 100
```

Multiple clients calling this script for different users will execute
concurrently, each holding a lock only on its own `user:<id>:quota` key.

## Redis Functions

Redis functions ([`FCALL`](/docs/redis/commands/functions/fcall),
[`FCALL_RO`](/docs/redis/commands/functions/fcall-ro)) also default to the global
lock. For functions, `allow-key-locking` is set on each registered function,
not on the library shebang, and takes effect when the library is loaded with
[`FUNCTION LOAD`](/docs/redis/commands/functions/function-load):

```lua
#!lua name=locks

local function incr_if_below(keys, args)
  local current = tonumber(redis.call('GET', keys[1]) or "0")
  if current >= tonumber(args[1]) then
    return 0
  end

  redis.call('INCR', keys[1])
  return 1
end

redis.register_function{
  function_name='incr_if_below',
  callback=incr_if_below,
  flags={'allow-key-locking'}
}
```

Invoked with:

```
FCALL incr_if_below 1 user:42:quota 100
```

The same rules apply: every key used by the function must be passed in the
`FCALL` key list. Keys passed as regular arguments are not locked unless they
also appear in the key list.

If the function is also read-only, include both flags in the function
registration:

```lua
flags={'no-writes', 'allow-key-locking'}
```

## Dynamic Keys and Latency

Pass every key a script or function touches through the key list of the call,
where it arrives as `KEYS`, even when you are not using `allow-key-locking`.
With the flag set, a key that was not declared is rejected outright. Without it
the call still succeeds, but it can be slow, and the slowdown is not limited to
the caller.

Upstash keeps data [in memory and on disk](/docs/redis/features/durability), and an
entry that has been idle long enough to be evicted from memory is read back from
disk on the next access. Declared keys are loaded before the script body starts
running, and the engine releases the lock while it waits for that read, so other
commands keep making progress. A key that only becomes known in the middle of
the script cannot be handled that way: script execution has to stay atomic, so
the engine holds the lock across the disk read. Under the global lock, that
means the whole database waits for a disk read in the middle of your script.
It is also why dynamic keys are rejected when `allow-key-locking` is set: a key
the engine was never told about can be neither locked nor loaded up front.

```lua
-- Dynamic key: resolved inside the script, may be read from disk under the lock
local key = 'user:' .. ARGV[1] .. ':quota'
redis.call('INCR', key)
```

```lua
-- Declared key: loaded before the script runs, outside the locked section
redis.call('INCR', KEYS[1])
```

The cost only appears when the key is not already in memory, so it is easy to
miss against a small, warm test dataset and easy to hit in production against a
large one. Resolve key names in your application and pass them in the key list.

# Replication
Source: https://upstash.com/docs/redis/features/replication

Replication is enabled for all paid Upstash databases. The data is replicated to
multiple instances. Replication provides you high availability and better
scalability.

### High Availability

Replication makes your database resilient to failures because even one of the
replicas is not available, your database continues to work.

There are two types of replicas in Upstash Redis: primary replicas and read replicas. Primary replicas handle both reads and writes, while read replicas are used only for reads.

In all subscription plans, primary replicas are highly available with multiple replicas to ensure that even if one fails, your database continues to function.

If a read replica fails, your database remains operational, and you can still read from the primary replicas, though with higher latency.
When [Prod Pack](/docs/redis/overall/enterprise#prod-pack-features) is enabled, replicas (including read replicas) are deployed across multiple availability zones. This gives regions multi-zone high availability: if one replica or zone fails, another replica in a different zone continues serving commands in the same region without any additional latency.

### Better Scalability

In a replicated database, your requests are evenly distributed among the
replicas using a round-robin approach. As your throughput requirements grow,
additional replicas can be added to the cluster to handle the increased workload
and maintain high performance. This scalability feature ensures that your
database can effectively meet the demands of high throughput scenarios.

### Architecture

We use the single leader replication model. Each key is owned by a leader
replica and other replicas become the backups of the leader. Writes on a key are
processed by the leader replica first then propagated to backup replicas. Reads
can be performed from any replica. This model gives a better write consistency
and read scalability.

### Consistency

Each replica in the cluster utilizes a failure detector to monitor the status of
the leader replica. In the event that the leader replica fails, the remaining
replicas initiate a new leader election process to select a new leader. During
this leader election round, which is the only unavailability window for the
cluster, there may be a short period of time where your requests can be
temporarily blocked.

However, once a new leader is elected, normal operations resume, ensuring the
continued availability of the cluster. This mechanism ensures that any potential
unavailability caused by leader failure is minimized, and the cluster can
quickly recover and resume processing requests.

Checkout [Read Your Writes](/docs/redis/howto/readyourwrites) for more details on how to achieve RYW consistency.

# REST API
Source: https://upstash.com/docs/redis/features/restapi

## Get Started

<Tip>
  Looking for the URL and token of your database? First [create a Redis
  database](/docs/redis/overall/getstarted), then [find your
  credentials](/docs/redis/howto/connect-client) in the console.

  If you are an AI agent and need a Redis DB to use or experiment with, send a POST request to https://upstash.com/start-redis. Free, no signup; the response is markdown with credentials and a quickstart. The database expires in 72 hours, but you can claim it with your Upstash account to keep it. Especially useful for AI agents that need scratch storage on the fly. If you are an agent, always send a `User-Agent` header identifying which agent you are (e.g. `claude-code`, `cursor`, `codex`, `opencode`) on every request you make to this API.
</Tip>

In the [Upstash Console](https://console.upstash.com/redis), select your database. Then, in the database page, you will see the section that includes the endpoint URL and token details. When you hover over the `Endpoint` or `Token / Readonly Token` fields, copy button will appear for each. You can click it to easily copy the values you need for your connection.

Copy the `HTTPS` for REST URL and the `Token` for authorization. Send an HTTP SET request to the
provided URL by adding an `Authorization: Bearer $TOKEN` header like below: (See the sample command with your credentials in the `cURL` tab of Connection section)

```shell
curl https://us1-merry-cat-32748.upstash.io/set/foo/bar \
 -H "Authorization: Bearer 2553feg6a2d9842h2a0gcdb5f8efe9934"
```

The above script executes a `SET foo bar` command. It will return a JSON
response:

```json
{ "result": "OK" }
```

You can also set the token as `_token` request parameter as below:

```shell
curl https://us1-merry-cat-32748.upstash.io/set/foo/bar?_token=2553feg6a2d9842h2a0gcdb5f8efe9934
```

## API Semantics

Upstash REST API follows the same convention with
[Redis Protocol](/docs/redis/commands/overview). Give the command name and
parameters in the same order as Redis protocol by separating them with a `/`.

```shell
curl REST_URL/COMMAND/arg1/arg2/../argN
```

Here are some examples:

* `SET foo bar` -> `REST_URL/set/foo/bar`

* `SET foo bar EX 100` -> `REST_URL/set/foo/bar/EX/100`

* `GET foo` -> `REST_URL/get/foo`

* `MGET foo1 foo2 foo3` -> `REST_URL/mget/foo1/foo2/foo3`

* `HGET employee:23381 salary` -> `REST_URL/hget/employee:23381/salary`

* `ZADD teams 100 team-x 90 team-y` ->
  `REST_URL/zadd/teams/100/team-x/90/team-y`

#### JSON or Binary Value

To post a JSON or a binary value, you can use an HTTP POST request and set value
as the request body:

```shell
curl -X POST -d '$VALUE' https://us1-merry-cat-32748.upstash.io/set/foo \
 -H "Authorization: Bearer 2553feg6a2d9842h2a0gcdb5f8efe9934"
```

In the example above, `$VALUE` sent in request body is appended to the command
as `REST_URL/set/foo/$VALUE`.

Please note that when making a POST request to the Upstash REST API, the request
body is appended as the last parameter of the Redis command. If there are
additional parameters in the Redis command after the value, you should include
them as query parameters in the request:

```shell
curl -X POST -d '$VALUE' https://us1-merry-cat-32748.upstash.io/set/foo?EX=100 \
 -H "Authorization: Bearer 2553feg6a2d9842h2a0gcdb5f8efe9934"
```

Above command is equivalent to `REST_URL/set/foo/$VALUE/EX/100`.

#### POST Command in Body

Alternatively, you can send the whole command in the request body as a single
JSON array. Array's first element must be the command name and command
parameters should be appended next to each other in the same order as Redis
protocol.

```shell
curl -X POST -d '[COMMAND, ARG1, ARG2,.., ARGN]' REST_URL
```

For example, Redis command `SET foo bar EX 100` can be sent inside the request
body as:

```shell
curl -X POST -d '["SET", "foo", "bar", "EX", 100]' https://us1-merry-cat-32748.upstash.io \
 -H "Authorization: Bearer 2553feg6a2d9842h2a0gcdb5f8efe9934"
```

## HTTP Codes

* `200 OK`: When request is accepted and successfully executed.

* `400 Bad Request`: When there's a syntax error, an invalid/unsupported command
  is sent or command execution fails.

* `401 Unauthorized`: When authentication fails; auth token is missing or
  invalid.

* `405 Method Not Allowed`: When an unsupported HTTP method is used. Only
  `HEAD`, `GET`, `POST` and `PUT` methods are allowed.

## Response

REST API returns a JSON response by default. When command execution is successful, response
JSON will have a single `result` field and its value will contain the Redis
response. It can be either;

* a `null` value

```json
{ "result": null }
```

* an integer

```json
{ "result": 137 }
```

* a string

```json
{ "result": "value" }
```

* an array value:

```json
{ "result": ["value1", null, "value2"] }
```

If command is rejected or fails, response JSON will have a single `error` field
with a string value explaining the failure:

```json
{"error":"WRONGPASS invalid password"}

{"error":"ERR wrong number of arguments for 'get' command"}
```

### Base64 Encoded Responses

If the response contains an invalid utf-8 character, it will be replaced with
a � (Replacement character U+FFFD). This can happen when you are using binary
operations like `BITOP NOT` etc.

If you prefer the raw response in base64 format, you can achieve this by setting
the `Upstash-Encoding` header to `base64`. In this case, all strings in the response
will be base64 encoded, except for the "OK" response.

```shell
curl https://us1-merry-cat-32748.upstash.io/SET/foo/bar \
 -H "Authorization: Bearer 2553feg6a2d9842h2a0gcdb5f8efe9934" \
 -H "Upstash-Encoding: base64"

# {"result":"OK"}

curl https://us1-merry-cat-32748.upstash.io/GET/foo \
 -H "Authorization: Bearer 2553feg6a2d9842h2a0gcdb5f8efe9934" \
 -H "Upstash-Encoding: base64"

# {"result":"YmFy"}
```

### RESP2 Format Responses

REST API returns a JSON response by default and the response content type is set to `application/json`.

If you prefer the binary response in RESP2 format, you can achieve this by setting
the `Upstash-Response-Format` header to `resp2`. In this case, the response content type
is set to `application/octet-stream` and the raw response is returned as binary similar to a TCP-based Redis client.

The default value for this option is `json`.
Any format other than `json` and `resp2` is not allowed and will result in a HTTP 400 Bad Request.

This option is not applicable to `/multi-exec` transactions endpoint, as it only returns response in JSON format.
Additionally, setting the `Upstash-Encoding` header to `base64` is not permitted when the `Upstash-Response-Format` is set to `resp2`
and will result in a HTTP 400 Bad Request.

```shell
curl https://us1-merry-cat-32748.upstash.io/SET/foo/bar \
 -H "Authorization: Bearer 2553feg6a2d9842h2a0gcdb5f8efe9934" \
 -H "Upstash-Reponse-Format: resp2"

# +OK\r\n

curl https://us1-merry-cat-32748.upstash.io/GET/foo \
 -H "Authorization: Bearer 2553feg6a2d9842h2a0gcdb5f8efe9934" \
 -H "Upstash-Reponse-Format: resp2"

# $3\r\nbar\r\n
```

## Pipelining

Upstash REST API provides support for command pipelining, allowing you to send
multiple commands as a batch instead of sending them individually and waiting
for responses. With the pipeline API, you can include several commands in a
single HTTP request, and the response will be a JSON array. Each item in the
response array corresponds to the result of a command in the same order as they
were included in the pipeline.

API endpoint for command pipelining is `/pipeline`. Pipelined commands should be
send as a two dimensional JSON array in the request body, each row containing
name of the command and its arguments.

**Request syntax**:

```shell
curl -X POST https://us1-merry-cat-32748.upstash.io/pipeline \
 -H "Authorization: Bearer $TOKEN" \
 -d '
    [
      ["CMD_A", "arg0", "arg1", ..., "argN"],
      ["CMD_B", "arg0", "arg1", ..., "argM"],
      ...
    ]
    '
```

**Response syntax**:

```json
[{"result":"RESPONSE_A"},{"result":"RESPONSE_B"},{"error":"ERR ..."}, ...]
```

<Note>
  Execution of the pipeline is _not atomic_. Even though each command in the
  pipeline will be executed in order, commands sent by other clients can
  interleave with the pipeline. Use [transactions](#transactions) API instead if
  you need atomicity.
</Note>

For example you can write the `curl` command below to send following Redis
commands using pipeline:

```redis
SET key1 valuex
SETEX key2 13 valuez
INCR key1
ZADD myset 11 item1 22 item2
```

```shell
curl -X POST https://us1-merry-cat-32748.upstash.io/pipeline \
 -H "Authorization: Bearer 2553feg6a2d9842h2a0gcdb5f8efe9934" \
 -d '
    [
      ["SET", "key1", "valuex"],
      ["SETEX", "key2", 13, "valuez"],
      ["INCR", "key1"],
      ["ZADD", "myset", 11, "item1", 22, "item2"]
    ]
    '
```

And pipeline response will be:

```json
[
  { "result": "OK" },
  { "result": "OK" },
  { "error": "ERR value is not an int or out of range" },
  { "result": 2 }
]
```

You can use pipelining when;

* You need more throughput, since pipelining saves from multiple round-trip
  times. (_But beware that latency of each command in the pipeline will be equal
  to the total latency of the whole pipeline._)
* Your commands are independent of each other, response of a former command is
  not needed to submit a subsequent command.

## Transactions

Upstash REST API supports transactions to execute multiple commands atomically.
With transactions API, several commands are sent using a single HTTP request,
and a single JSON array response is returned. Each item in the response array
corresponds to the command in the same order within the transaction.

API endpoint for transaction is `/multi-exec`. Transaction commands should be
send as a two dimensional JSON array in the request body, each row containing
name of the command and its arguments.

**Request syntax**:

```shell
curl -X POST https://us1-merry-cat-32748.upstash.io/multi-exec \
 -H "Authorization: Bearer $TOKEN" \
 -d '
    [
      ["CMD_A", "arg0", "arg1", ..., "argN"],
      ["CMD_B", "arg0", "arg1", ..., "argM"],
      ...
    ]
    '
```

**Response syntax**:

In case when transaction is successful, multiple responses corresponding to each
command is returned in json as follows:

```json
[{"result":"RESPONSE_A"},{"result":"RESPONSE_B"},{"error":"ERR ..."}, ...]
```

If transaction is discarded as a whole, a single error is returned in json as
follows:

```json
{ "error": "ERR ..." }
```

A transaction might be discarded in following cases:

* There is a syntax error on the transaction request.
* At least one of the commands is unsupported.
* At least one of the commands exceeds the
  [max request size](../troubleshooting/max_request_size_exceeded).
* At least one of the commands exceeds the
  [daily request limit](../troubleshooting/max_daily_request_limit).

Note that a command may still fail even if it is a supported and valid command.
In that case, all commands will be executed. Upstash Redis will not stop the
processing of commands. This is to provide same semantics with Redis when there
are
[errors inside a transaction](https://redis.io/docs/manual/transactions/#errors-inside-a-transaction).

**Example**:

You can write the `curl` command below to send following Redis commands using
REST transaction API:

```
MULTI
SET key1 valuex
SETEX key2 13 valuez
INCR key1
ZADD myset 11 item1 22 item2
EXEC
```

```shell
curl -X POST https://us1-merry-cat-32748.upstash.io/multi-exec \
 -H "Authorization: Bearer 2553feg6a2d9842h2a0gcdb5f8efe9934" \
 -d '
    [
      ["SET", "key1", "valuex"],
      ["SETEX", "key2", 13, "valuez"],
      ["INCR", "key1"],
      ["ZADD", "myset", 11, "item1", 22, "item2"]
    ]
    '
```

And transaction response will be:

```json
[
  { "result": "OK" },
  { "result": "OK" },
  { "error": "ERR value is not an int or out of range" },
  { "result": 2 }
]
```

## Monitor Command

Upstash REST API provides Redis [`MONITOR`](/docs/redis/commands/server/monitor) command using
[Server Send Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) mechanism. API endpoint is `/monitor`.

```shell
curl -X POST https://us1-merry-cat-32748.upstash.io/monitor \
  -H "Authorization: Bearer 2553feg6a2d9842h2a0gcdb5f8efe9934" \
  -H "Accept:text/event-stream"
```

This request will listen for Redis monitor events and incoming data will be received as:

```
data: "OK"
data: 1721284005.242090 [0 0.0.0.0:0] "GET" "k"
data: 1721284008.663811 [0 0.0.0.0:0] "SET" "k" "v"
data: 1721284025.561585 [0 0.0.0.0:0] "DBSIZE"
data: 1721284030.601034 [0 0.0.0.0:0] "KEYS" "*"
```

## Subscribe & Publish Commands

Simiar to `MONITOR` command, Upstash REST API provides Redis [`SUBSCRIBE`](/docs/redis/commands/pub-sub/subscribe) and
[`PUBLISH`](/docs/redis/commands/pub-sub/publish) commands. The `SUBSCRIBE` endpoint works using
[Server Send Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) mechanism.
API endpoints are `/subscribe` and `/publish`

Following request will subscribe to a channel named `chat`:

```shell
curl -X POST https://us1-merry-cat-32748.upstash.io/subscribe/chat \
  -H "Authorization: Bearer 2553feg6a2d9842h2a0gcdb5f8efe9934" \
  -H "Accept:text/event-stream"
```

Following request will publish to a channel named `chat`:

```shell
curl -X POST https://us1-merry-cat-32748.upstash.io/publish/chat/hello \
  -H "Authorization: Bearer 2553feg6a2d9842h2a0gcdb5f8efe9934"
```

The subscriber will receive incoming messages as:

```
data: subscribe,chat,1
data: message,chat,hello
data: message,chat,how are you today?
```

## Security and Authentication

You need to add a header to your API requests as `Authorization: Bearer $TOKEN`
or set the token as a url parameter `_token=$TOKEN`.

```shell
curl -X POST https://us1-merry-cat-32748.upstash.io/info \
  -H "Authorization: Bearer 2553feg6a2d9842h2a0gcdb5f8efe9934"
```

OR

```shell
curl -X POST https://us1-merry-cat-32748.upstash.io/info?_token=2553feg6a2d9842h2a0gcdb5f8efe9934
```

Upstash by default provides two separate access tokens per database: "Standard"
and "Read Only".

* **Standard** token has full privilege over the database, can execute any
  command.

* **Read Only** token permits access to the read commands only. Some powerful
  read commands (e.g. SCAN, KEYS) are also restricted with read only token. It
  makes sense to use _Read Only_ token when you access Upstash Redis from web
  and mobile clients where the token is exposed to public.

You can get/copy the tokens by clicking the copy button next to
`UPSTASH_REDIS_REST_TOKEN` in the **Connect** section of the console (select the
**REST** tab). For the _Read Only_ token, just enable the "Read-Only Token" switch.

  <img />

<Warning>
  Do not expose your _Standard_ token publicly. _Standard_ token has full
  privilege over the database. You can expose the _Read Only_ token as it has
  access to read commands only. You can revoke both _Standard_ and _Read Only_
  tokens by resetting password of your database.
</Warning>

### REST Token for ACL Users

In addition to the tokens provided by default, you can create REST tokens for
the users created via [`ACL SETUSER`](/docs/redis/commands/server/acl-setuser)
command. Upstash provides a custom `ACL` subcommand to generate REST tokens:
[`ACL RESTTOKEN`](/docs/redis/commands/server/acl-resttoken). It expects two arguments; username and user's password. And
returns the REST token for the user as a string response.

```
ACL RESTTOKEN <username> <password>
    Generate a REST token for the specified username & password.
    Token will have the same permissions with the user.
```

You can execute `ACL RESTTOKEN` command via `redis-cli`:

```shell
redis-cli> ACL RESTTOKEN default 35fedg8xyu907d84af29222ert
"AYNgAS2553feg6a2d9842h2a0gcdb5f8efe9934DQ="
```

Or via CLI on the Upstash console:

  <img />

If the user doesn't exist or password doesn't match then an error will be
returned.

```shell
redis-cli> ACL RESTTOKEN upstash fakepass
(error) ERR Wrong password or user "upstash" does not exist
```

## Redis Protocol vs REST API

### REST API Pros

* If you want to access to Upstash database from an environment like CloudFlare
  Workers, WebAssembly, Fastly Compute@Edge then you can not use Redis protocol
  as it is based on TCP. You can use REST API in those environments.

* REST API is request (HTTP) based where Redis protocol is connection based. If
  you are running serverless functions (AWS Lambda etc), you may need to manage
  the Redis client's connections. REST API does not have such an issue.

* Redis protocol requires Redis clients. On the other hand, REST API is
  accessible with any HTTP client.

### Redis Protocol Pros

* If you have legacy code that relies on Redis clients, the Redis protocol
  allows you to utilize Upstash without requiring any modifications to your
  code.

* By leveraging the Redis protocol, you can take advantage of the extensive
  Redis ecosystem. For instance, you can seamlessly integrate your Upstash
  database as a session cache for your Express application.

## Cost and Pricing

Upstash pricing is based on per command/request. So the same pricing listed in
our [pricing](https://upstash.com/pricing/redis) applies to your REST calls too.

## Metrics and Monitoring

In the current version, we do not expose any metrics specific to API calls in
the console. But the metrics of the database backing the API should give a good
summary about the performance of your APIs.

## REST - Redis API Compatibility

| Feature                                                       | REST Support? |                               Notes                               |
| ------------------------------------------------------------- | :-----------: | :---------------------------------------------------------------: |
| [String](/docs/redis/commands/string/overview)             |      ✅       |                                                                   |
| [Bitmap](/docs/redis/commands/bitmap/overview)             |      ✅       |                                                                   |
| [Hash](/docs/redis/commands/hash/overview)                 |      ✅       |                                                                   |
| [List](/docs/redis/commands/list/overview)                 |      ✅       | Blocking commands (BLPOP - BRPOP - BRPOPLPUSH) are not supported. |
| [Set](/docs/redis/commands/set/overview)                   |      ✅       |                                                                   |
| [SortedSet](/docs/redis/commands/sorted-set/overview)      |      ✅       |    Blocking commands (BZPOPMAX - BZPOPMIN) are not supported.     |
| [Geo](/docs/redis/commands/geo/overview)                   |      ✅       |                                                                   |
| [HyperLogLog](/docs/redis/commands/hyperloglog/overview)   |      ✅       |                                                                   |
| [Transactions](/docs/redis/commands/transactions/overview) |      ✅       |             WATCH/UNWATCH/DISCARD are not supported               |
| [Generic](/docs/redis/commands/generic/overview)           |      ✅       |                                                                   |
| [Server](/docs/redis/commands/server/overview)             |      ✅       |                                                                   |
| [Scripting](/docs/redis/commands/scripting/overview)       |      ✅       |                                                                   |
| [Pub/Sub](/docs/redis/commands/pub-sub/overview)            |      ✅       |                                                                   |
| [Connection](/docs/redis/commands/connection/overview)     |      ⚠️       |                 Only PING and ECHO are supported.                 |
| [JSON](/docs/redis/commands/json/overview)                 |      ✅       |                                                                   |
| [Streams](/docs/redis/commands/streams/overview)            |      ✅       |    Supported, except blocking versions of XREAD and XREADGROUP.   |
| Cluster                                               |      ❌       |                                                                   |

# Security
Source: https://upstash.com/docs/redis/features/security

Upstash has a set of features to help you secure your data. We will list them
and at the end of the section we will list the best practices to improve
security of database.

## TLS

TLS is always enabled on Upstash Redis databases. The data transfer between the client and database is
encrypted.

## Redis ACL

With Redis ACL, you can improve security by restricting a user's access to
commands and keys, so that untrusted clients have no access and trusted clients
have just the minimum required access level to the database. Moreover it
improves operational safety, so that clients or users accessing Redis are not
allowed to damage the data or the configuration due to errors or mistakes. Check
[Redis ACL documentation](https://redis.io/docs/latest/operate/oss_and_stack/management/security/acl/). If you
are using the REST API, you can still benefit from ACLs as explained
[here](/docs/redis/features/restapi#rest-token-for-acl-users)

ACL is available on all paid databases.

## Database Credentials

When you create a database, a secure password is generated. Upstash keeps the
password encrypted. Use environment variables or your provider's secret
management system (e.g. AWS Secrets Manager, Vercel Secrets) to keep them. Do
not use them hardcoded in your code. If your password is leaked, reset the
password using Upstash console.

## Encryption at Rest

Encryption at Rest encrypts the block storage where your data is persisted and
stored. It is available with [Prod Pack](/docs/redis/overall/enterprise#prod-pack-features) add-on.

## Application Level Encryption

Client side encryption can be used to encrypt data through application
lifecycle. Client-side encryption is used to help protect data in use. This
comes with some limitations. Operations that must operate on the data, such as
increments, comparisons, and searches will not function properly. You can write
client-side encryption logic directly in your own application or use functions
built into clients such as the Java Lettuce cipher codec. We have plans to
support encryption in our SDKs.

## IP Allowlisting

We can restrict the access to your database to a set of IP addresses which will
have access to your database. This is quite a strong way to secure your
database, but it has some limitations. For example you can not know the IP
addresses in serverless platforms such AWS Lambda and Vercel functions.

## VPC Peering

VPC Peering enables you to connect to Upstash from your own VPC using private
IP. Database will not be accessible from the public network. Database and your
application can run in the same subnet which also minimizes data transfer costs.
VPC Peering is only available for Pro databases.

## Private Link

AWS Private link provides private connectivity between Upstash Database and your
Redis client inside AWS infrastructure. Private link is only available for
Pro databases.

# Compliance
Source: https://upstash.com/docs/redis/help/compliance

## Upstash Legal & Security Documents

* [Upstash Terms of Service](https://upstash.com/static/trust/terms.pdf)
* [Upstash Privacy Policy](https://upstash.com/static/trust/privacy.pdf)
* [Upstash Data Processing Agreement](https://upstash.com/static/trust/dpa.pdf)
* [Upstash Technical and Organizational Security Measures](https://upstash.com/static/trust/security-measures.pdf)
* [Upstash Subcontractors](https://upstash.com/static/trust/subprocessors.pdf)

## Is Upstash SOC2 Compliant?

As of July 2023, Upstash Redis is SOC2 compliant. Check our [trust page](https://trust.upstash.com/) for details.

## Is Upstash ISO-27001 Compliant?

We are in process of getting this certification. Contact us
([support@upstash.com](mailto:support@upstash.com)) to learn about the expected
date.

## Is Upstash GDPR Compliant?

Yes. For more information, see our
[Privacy Policy](https://upstash.com/static/trust/privacy.pdf). We acquire DPAs
from each [subcontractor](https://upstash.com/static/trust/subprocessors.pdf)
that we work with.

## Is Upstash HIPAA Compliant?

Upstash is currently not HIPAA compliant. Contact us
([support@upstash.com](mailto:support@upstash.com)) if HIPAA is important for
you and we can share more details.

## Is Upstash PCI Compliant?

Upstash does not store personal credit card information. We use Stripe for
payment processing. Stripe is a certified PCI Service Provider Level 1, which is
the highest level of certification in the payments industry.

## Does Upstash conduct vulnerability scanning and penetration tests?

Yes, we use third party tools and work with pen testers. We share the results
with Enterprise customers. Contact us
([support@upstash.com](mailto:support@upstash.com)) for more information.

## Does Upstash take backups?

Yes, we take regular snapshots of the data cluster to the AWS S3 platform.

## Does Upstash encrypt data?

Customers can enable TLS while creating database/cluster, and we recommend it
for production databases/clusters. Also we encrypt data at rest at request of
customers.

# Frequently Asked Questions
Source: https://upstash.com/docs/redis/help/faq

## What is Upstash Redis?

Upstash is a serverless database service compatible with Redis® API.

## What is a Serverless Database?

* You do not have to manage and provision servers.
* You do not deal with configuring or maintaining any server.
* You just use the service and pay what you use. If you are not using it, you should not be paying.

## What are the use cases?

Upstash works for all the common usecases for Redis®. You can use Upstash in your serverless stack. In addition, you can use Upstash as storage (or caching) for your serverless functions. See [Use Cases](/docs/redis/overall/usecases) for more.

## Do you support all Redis® API?

Most of them. See [Redis® API Compatibility](/docs/redis/overall/compatibility) for the list of supported commands.

## Can I use any Redis client?

Yes, Upstash is compatible Redis client protocol.

## Which cloud providers do you support?

Upstash Redis is available on AWS, GCP and Fly.io.

## Which regions do you support in AWS?

We start with AWS-US-EAST-1 (Virginia), GCP-US-CENTRAL-1 (IOWA), AWS-US-WEST-1 (N. California), AWS-EU-WEST-1 (Ireland), AWS-APN-NE-1 (Japan). We will add new regions soon. You can expedite this by telling us your use case and the region you need by emailing to [support@upstash.com](mailto:support@upstash.com)

## Should my client be hosted in the AWS to use Upstash?

No. Your client can be anywhere but the clients in AWS regions will give you better performance.

## How do you compare Upstash with ElastiCache?

Upstash is serverless. With ElastiCache, you pay even you do not use the database. See [Compare](/docs/redis/overall/compare) for more info.

## How do you compare Upstash with Redis Labs or Compose.io?

Upstash is serverless. With Redis Labs or Compose.io, you always pay a lot when your data size is big but your traffic is low. In Upstash, the pricing is based on per request. See [Compare](/docs/redis/overall/compare) for more info.

## Do you persist data?

Yes, by default we write data to the disk. So in case of a failure you should not lose any data.

## Do you support Redis Cluster?

We support replication in Premium type database. We do not support sharding yet.

## I have database with 10GB data, I pay nothing if I do not use it. Is that correct?

You only pay for the disk storage cost that is \$0.25 per GB. For your case, you will pay \$2.5 monthly.

## What happens when I exceed the request limit on Free Database (10.000 requests per day)?

The exceeding commands return exception.

## What happens if my database exceeds the max commands-per-second limit?

When your database reaches its commands-per-second limit, requests are temporarily throttled.
In most cases, commands wait until capacity becomes available again, which may increase latency for a short time. Once traffic drops below the limit (or the plan is upgraded), normal request flow resumes.

## When I upgrade my free database, do I lose data?

You do not lose data but clients may disconnect and reconnect.

## Upstash is much cheaper than Elasticache and Redis Labs for big data sizes (> 10GB). How is that possible?

Upstash storage layer is multi tiered. We keep your data in both memory and block storage (disk). The entries that are not accessed frequently are removed from the memory but stored in disk. Latency overhead of idle entries is limited thanks to the SSD based storage. Multi tiered storage allows us to provide more flexible pricing.

## Will my data be safe?

Upstash is a GDPR compliant company. We do not share any user data with third parties. See our [Legal Documents](/docs/common/help/legal) for more information.

## What happens if my database is not used?

Free tier databases are archived after a minimum of 30 days of inactivity. Users get several warning emails prior to this operation. Archival means backing up user data and removing the database instance.

If you wish to keep your database endpoint active for a longer period of inactivity, consider switching to a paid plan.

<Note>
  No data is lost due to archival. When a free tier database is archived due to inactivity, we take a backup of the data so users can create a new database and restore their data from the Upstash Console.
</Note>

## How do you handle the noisy neighbour problem? Do other tenants affect my database?

Databases are isolated on some aspects but still share some hardware resources such as CPU or network. To avoid noisy neighbor influence on these resources, there are specific quotas for each database. When they reach any of these quotas they are throttled using a backoff strategy. When multiple databases sharing the same hardware are close to the limits, our system can add new resources to the pool and/or migrate some of the databases to distribute the load.

Also if a database exceeds its quotas very frequently, we notify users whether they want to upgrade to an upper plan. Databases in enterprise plans are placed either on dedicated or more isolated hardware due to higher resource needs.

# Integration with Third Parties & Partnerships
Source: https://upstash.com/docs/redis/help/integration

  <img height="100" />

## Introduction

In this guideline we will outline the steps to integrate Upstash into your platform (GUI or Web App) and allow your users to create and manage Upstash databases without leaving your interfaces. We will explain how to use OAuth2.0 as the underlying foundation to enable this access seamlessly.

If your product or service offering utilizes Redis, Vector or QStash or if there is a common use case that your end users enable by leveraging these database resources, we invite you to be a partner with us. By integrating Upstash into your platform, you can offer a more complete package for your customers and become a one stop shop. This will also position yourself at the forefront of innovative cloud computing trends such as serverless and expand your customer base.

This is the most commonly used partnership integration model that can be easily implemented by following this guideline. Recently [Cloudflare workers integration](https://blog.cloudflare.com/cloudflare-workers-database-integration-with-upstash/) is implemented through this methodology. For any further questions or partnership discussions please send us an email at partnerships@upstash.com

<Info>
  Before starting development to integrate Upstash into your product, please
  send an email to partnerships@upstash.com for further assistance and guidance.
</Info>

**General Flow (High level user flow)**

1. User clicks **`Connect Upstash`** button on your platform’s surface (GUI, Web App)
2. This initiates the OAuth 2.0 flow, which opens a new browser page displaying the **`Upstash Login Page`**.
3. If this is an existing user, user logins with their Upstash credentials otherwise they can directly sign up for a new Upstash account.
4. Browser window redirects to **`Your account has been connected`** page and authentication window automatically closes.
5. After the user returns to your interface, they see their Upstash Account is now connected.

## Technical Design (SPA - Regular Web Application)

1. Users click `Connect Upstash` button from Web App.
2. Web App initiate Upstash OAuth 2.0 flow. Web App can use
   [Auth0 native libraries](https://auth0.com/docs/libraries).

<Note>
  Please reach partnerships@upstash.com to receive client id and callback url.
</Note>

3. After user returns from OAuth 2.0 flow then web app will have JWT token. Web
   App can generate Developer Api key:

```bash
curl -XPOST https://api.upstash.com/apikey \
    -H "Authorization: Bearer JWT_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "name": "APPNAME_API_KEY_TIMESTAMP" }'
```

4. Web App need to save Developer Api Key to the backend.

## Technical Design ( GUI Apps )

  <img />

1. User clicks **`Connect Upstash`** button from web app.
2. Web app initiates Upstash OAuth 2.0 flow and it can use **[Auth0 native libraries](https://auth0.com/docs/libraries)**.
3. App will open new browser:

```
https://auth.upstash.com/authorize?response_type=code&audience=upstash-api&scope=offline_access&client_id=XXXXXXXXXX&redirect_uri=http%3A%2F%2Flocalhost:3000
```

<Note>Please reach partnerships@upstash.com to receive client id.</Note>

4. After user authenticated Auth0 will redirect user to
   `localhost:3000/?code=XXXXXX`
5. APP can return some nice html response when Auth0 returns to `localhost:3000`

6. After getting `code` parameter from the URL query, GUI App will make http
   call to the Auth0 code exchange api. Example CURL request

```bash
curl -XPOST 'https://auth.upstash.com/oauth/token' \
  --header 'content-type: application/x-www-form-urlencoded' \
  --data 'grant_type=authorization_code --data audience=upstash-api' \
  --data 'client_id=XXXXXXXXXXX' \
  --data 'code=XXXXXXXXXXXX' \
  --data 'redirect_uri=localhost:3000'
```

Response:

```json
{
  "access_token": "XXXXXXXXXX",
  "refresh_token": "XXXXXXXXXXX",
  "scope": "offline_access",
  "expires_in": 172800,
  "token_type": "Bearer"
}
```

7. After 6th Step the response will include `access_token`, it has 3 days TTL.
   GUI App will call Upstash API to get a developer api key:

```bash
curl https://api.upstash.com/apikey -H "Authorization: Bearer JWT_KEY" -d '{ "name" : "APPNAME_API_KEY_TIMESTAMP" }'
```

8. GUI App will save Developer Api key locally. Then GUI App can call any
   Upstash Developer API [developer.upstash.com/](https://developer.upstash.com/)

## Managing Resources

After obtaining Upstash Developer Api key, your platform surface (web or GUI) can call Upstash API. For example **[Create Database](https://developer.upstash.com/#create-database-global)**, **[List Database](https://developer.upstash.com/#list-databases)**

In this flow, you can ask users for region information and name of the database then can call Create Database API to complete the task

Example CURL request:

```bash
curl -X POST \
  https://api.upstash.com/v2/redis/database \
  -u 'EMAIL:API_KEY' \
  -d '{"name":"myredis", "region":"global", "primary_region":"us-east-1", "read_regions":["us-west-1","us-west-2"], "tls": true}'
```

# Legal
Source: https://upstash.com/docs/redis/help/legal

## Upstash Legal Documents

* [Upstash Terms of Service](https://upstash.com/trust/terms.pdf)
* [Upstash Privacy Policy](https://upstash.com/trust/privacy.pdf)
* [Upstash Subcontractors](https://upstash.com/trust/subprocessors.pdf)

# Managing Healthcare Data
Source: https://upstash.com/docs/redis/help/managing-healthcare-data

You can use Upstash Redis to store and process Protected Health Information (PHI). You are responsible for the following:

* **Signing a Business Associate Agreement (BAA)** with Upstash. This is provided as part of our Enterprise offering. Email [support@upstash.com](mailto:support@upstash.com) to get started.
* **Marking specific databases as HIPAA databases** and addressing security issues raised by the Upstash team.
* **Ensuring MFA is enabled** on all Upstash Console accounts.
  * Enforce MFA as a requirement to access the organization
* **Enabling Prod Pack** which provides encryption at rest and advanced security features (already included in the Enterprise plan).
* **Enabling Credential Protection** to prevent storing credentials in Upstash infrastructure and limit console access requiring database credentials.
* **Configuring IP allowlist** to restrict database access to authorized networks.
* **Enabling daily backups** to validate recoverability and meet retention requirements.
* **Complying with encryption requirements** in the HIPAA Security Rule. Data is encrypted at rest and in transit by Upstash. You can consider encrypting the data at your application layer.
* **Ensuring that PHI is stored only within your database**. Storing PHI in resource names or other locations is strictly prohibited.
* **Ensuring that PHI is stored only in values of data structures, not in identifiers or keys**. Avoid logging keys anywhere.
* **Not using public endpoints** to process PHI.
* **Not transferring databases** to a non-HIPAA organization.

<Note>
  For a comprehensive guide on implementing these responsibilities in production, see our [Production Checklist](/docs/redis/help/production-checklist). For questions about managing healthcare data, contact our support team at [support@upstash.com](mailto:support@upstash.com).
</Note>

# Production Checklist
Source: https://upstash.com/docs/redis/help/production-checklist

This checklist provides essential recommendations for securing and optimizing your Upstash databases for production workloads.

## Security Features

### Enable Prod Pack
Prod Pack provides enterprise-grade security and monitoring features:

* 99.99% uptime SLA
* SOC-2 Type 2 report available
* Encryption at Rest
* Advanced monitoring (Prometheus, Datadog)
* Multi-Zone High Availability
* High availability for read regions

<Note>
  Prod Pack is available as a $200/month add-on per database for all paid plans except Free tier. It is already included within Enterprise Plan.
</Note>

### Enable Credential Protection
Protect your database credentials (Prod Pack feature):

* Credentials are never stored in Upstash infrastructure
* Credentials are displayed only once during enablement
* Console features requiring database access are disabled

<Warning>
  Disabling this feature will permanently revoke current credentials and generate new ones.
</Warning>

### Configure IP Allowlist
Restrict database access to specific IP addresses:

* Available on all plans except Free tier
* Supports IPv4 addresses and CIDR blocks
* Multiple IP ranges can be configured

### Implement Redis ACL
Use Redis Access Control Lists to restrict user access:

* Available on all paid plans
* Create users with minimal required permissions
* Available for both TCP connections and REST API
* Use `ACL RESTTOKEN` command to generate REST tokens

### Enable Multi-Factor Authentication
Enable MFA on your Upstash account for enhanced security:

* Use your existing authentication provider (Google, GitHub, Amazon)
* Consider using a dedicated email/password account for production
* Force MFA for all team members to ensure consistent security
* Regularly review account access and team member permissions

### Secure Credential Management
Follow these best practices:

* Never hardcode credentials in your application code
* Use environment variables or secret management systems
* Reset passwords immediately if credentials are compromised
* Use Read-Only tokens for public-facing applications

## Network Security

### TLS Encryption
TLS is always enabled on Upstash Redis databases.

### VPC Peering (Enterprise)
Connect databases to your VPCs using private IP:

* Database becomes inaccessible from public networks
* Minimizes data transfer costs
* Available for Enterprise customers

## Monitoring & Observability

### Enable Advanced Monitoring
Prod Pack includes comprehensive monitoring:

* Prometheus integration
* Datadog integration
* Extended console metrics (up to one month)

## High Availability & Backup

### Enable Daily Backups
Configure automated daily backups for data protection:

* Available on all paid plans
* Backup retention up to 3 days with Prod Pack
* Hourly backups with customizable retention (Enterprise)

### Global Replication
For global applications, consider using Global Database:

* Distribute data across multiple regions
* Minimize latency for users worldwide
* Enhanced disaster recovery capabilities

## Compliance & Governance

### SOC-2 Compliance
Prod Pack and Enterprise plans include SOC-2 Type 2 compliance:

* Request SOC-2 report from [trust.upstash.com](https://trust.upstash.com/)
* Available for production workloads

### Enterprise Features
For enterprise customers:

* HIPAA compliance available
* SAML SSO integration
* Access logs available
* Custom resource allocation

## Pre-Production Checklist

Before going live, ensure you have:

* [ ] Prod Pack enabled (strongly recommended)
* [ ] Credential Protection enabled
* [ ] IP Allowlist configured
* [ ] MFA enabled on your account
* [ ] Daily backups enabled
* [ ] Monitoring and alerts configured
* [ ] Environment variables secured
* [ ] Error handling tested

## Additional Resources

* [Security Features](/docs/redis/features/security)
* [Prod Pack & Enterprise](/docs/redis/overall/enterprise)
* [Backup & Restore](/docs/redis/features/backup)
* [Global Database](/docs/redis/features/globaldatabase)
* [Monitoring & Metrics](/docs/redis/howto/metrics-and-charts)
* [Compliance Information](/docs/common/help/compliance)
* [Professional Support](/docs/common/help/prosupport)

For additional assistance with production deployment, contact our support team at [support@upstash.com](mailto:support@upstash.com).

# Shared Responsibility Model
Source: https://upstash.com/docs/redis/help/shared-responsibility-model

The Shared Responsibility Model defines the security and operational responsibilities between Upstash and our customers when using Upstash Redis. This model ensures clarity in who is responsible for what aspects of security, compliance, and operations.

## Overview

Upstash Redis is a serverless database service that provides Redis® API compatibility with automatic scaling, high availability, and enterprise-grade security features. The shared responsibility model divides responsibilities into three main categories:

* **Upstash Responsibilities**: Infrastructure, platform, and service-level security
* **Customer Responsibilities**: Data, application, and access management
* **Shared Responsibilities**: Configuration, monitoring, and incident response

## Responsibility Matrix

| Category | Upstash | Customer | Shared |
|----------|---------|----------|--------|
| **Infrastructure Security** | ✅ Physical security, network infrastructure, DDoS protection, hardware maintenance | ❌ | ❌ |
| **Platform Security** | ✅ OS security, Redis updates, container security, infrastructure monitoring | ❌ | ❌ |
| **Service Availability** | ✅ 99.99% SLA (Prod Pack), multi-zone high availability (Prod Pack), multi-region replication, auto-scaling, disaster recovery | ❌ | ❌ |
| **Data Encryption** | ✅ TLS in transit, encryption at rest (Prod Pack), key management | ❌ | ❌ |
| **Compliance** | ✅ SOC 2 (Prod Pack), GDPR, HIPAA (Enterprise) | ❌ | ❌ |
| **Data Management** | ❌ | ✅ Data classification, retention policies, quality controls | ❌ |
| **Application Security** | ❌ | ✅ Secure development, input validation, authentication, client-side encryption | ❌ |
| **Access Control** | ❌ | ✅ Redis ACL, user permissions, credential management, MFA | ❌ |
| **Network Security** | ❌ | ✅ IP allowlist, network segmentation, client security | ❌ |
| **Security Configuration** | ❌ | ❌ | ✅ ACL setup, security policies |
| **Monitoring** | ✅ Infrastructure monitoring, incident response | ✅ Application monitoring, custom metrics | ✅ Performance monitoring, security monitoring |
| **Incident Response** | ✅ Infrastructure incidents, service restoration | ✅ Application incidents, data incidents | ✅ Incident coordination, root cause analysis |

## Key Responsibilities

<AccordionGroup>
  <Accordion title="Upstash Responsibilities">
    **Infrastructure & Platform:**
    * Physical security, network infrastructure, DDoS protection
    * OS security, Redis updates, container security
    * 99.99% uptime SLA (Prod Pack), multi-zone high availability for read regions (Prod Pack), multi-region replication, auto-scaling
    * TLS encryption, encryption at rest (Prod Pack), key management
    * SOC 2 (Prod Pack), GDPR, HIPAA (Enterprise)
    * 24/7 infrastructure monitoring and incident response
  </Accordion>

  <Accordion title="Customer Responsibilities">
    **Data & Application Security:**
    * Architecture: retries/backoff, idempotency, timeouts; region/topology choices
    * Data governance: classification, retention, integrity
    * App security: secure coding, input validation, authN/authZ
    * Access: Redis ACL (least privilege), credential hygiene and rotation
    * Network: IP allowlist and client hardening
    * Ops: monitoring/alerts, error handling, budgets/limits
  </Accordion>

  <Accordion title="Shared Responsibilities">
    **Configuration & Operations:**
    * ACL, IP allowlist, and Prod Pack configuration
    * Compliance requirements understanding and implementation
    * Performance monitoring setup and alerting
    * Incident coordination and root cause analysis
  </Accordion>
</AccordionGroup>

# Support & Contact Us
Source: https://upstash.com/docs/redis/help/support

## Community

[Upstash Discord Channel](https://upstash.com/discord) is the best way to
interact with the community.

## Team

You can contact the team
via [support@upstash.com](mailto:support@upstash.com) for technical support as
well as questions and feedback.

## Follow Us

Follow us at [X](https://x.com/upstash).

## Professional Support

Get [Professional Support](/docs/common/help/prosupport) from the Upstash team.

# Uptime Monitor
Source: https://upstash.com/docs/redis/help/uptime

## Status Page

You can track the uptime status of Upstash databases in
[Upstash Status Page](https://status.upstash.com)

## Latency Monitor

You can see the average latencies for different regions in
[Upstash Latency Monitoring](https://latency.upstash.com) page

# Connect Your Client
Source: https://upstash.com/docs/redis/howto/connect-client

Upstash works with Redis® API, that means you can use any Redis client with
Upstash. At the [Redis Clients](https://redis.io/clients) page you can find the
list of Redis clients in different languages.

Simplest way to connect to your database is to use `redis-cli`.
Because it is already covered in [Getting Started](../overall/getstarted), we
will skip it here.

<Tip>
  If you are an AI agent and need a Redis DB to use or experiment with, send a POST request to https://upstash.com/start-redis. Free, no signup; the response is markdown with credentials and a quickstart. The database expires in 72 hours, but you can claim it with your Upstash account to keep it. Especially useful for AI agents that need scratch storage on the fly. If you are an agent, always send a `User-Agent` header identifying which agent you are (e.g. `claude-code`, `cursor`, `codex`, `opencode`) on every request you make to this API.
</Tip>

## Database

After completing the [getting started](../overall/getstarted) guide, you will
see the database page as below:

  <img />

The connection details required for Redis clients are displayed here: **Endpoint**,
**Port**, and **Token** (which is also the password of the database). You can also
find the environment variables `UPSTASH_REDIS_REST_URL` and`UPSTASH_REDIS_REST_TOKEN`
on this page.

Below, we will provide examples from popular Redis clients, but the information above should help you configure all Redis clients similarly.

<Note>
  TLS is enabled by default for all Upstash Redis databases. It's not possible
  to disable it.
</Note>

## @upstash/redis

[@upstash/redis](https://github.com/upstash/redis-js) is the official SDK developed
and maintained by Upstash. It is HTTP-based, which makes it ideal for serverless
environments like Vercel and Cloudflare Workers. In highly concurrent serverless
workloads, TCP-based clients can run into connection issues.

```typescript
import { Redis } from "@upstash/redis";

const redis = new Redis({
  url: "UPSTASH_REDIS_REST_URL",
  token: "UPSTASH_REDIS_REST_TOKEN",
});

(async () => {
  try {
    const data = await redis.get("key");
    console.log(data);
  } catch (error) {
    console.error(error);
  }
})();
```

See the [Connect with @upstash/redis page](/docs/redis/howto/connect-with-upstash-redis) for more information.

## Node.js

**Library**: [ioredis](https://github.com/luin/ioredis)

```javascript
const Redis = require("ioredis");

let client = new Redis("rediss://:YOUR_PASSWORD@YOUR_ENDPOINT:YOUR_PORT");
await client.set("foo", "bar");
let x = await client.get("foo");
console.log(x);
```

## Python

**Library**: [redis-py](https://github.com/andymccurdy/redis-py)

```python
import redis
r = redis.Redis(
host= 'YOUR_ENDPOINT',
port= 'YOUR_PORT',
password= 'YOUR_PASSWORD',
ssl=True)
r.set('foo','bar')
print(r.get('foo'))
```

## Java

**Library**: [jedis](https://github.com/xetorthio/jedis)

```java
Jedis jedis = new Jedis("YOUR_ENDPOINT", "YOUR_PORT", true);
jedis.auth("YOUR_PASSWORD");
jedis.set("foo", "bar");
String value = jedis.get("foo");
System.out.println(value);
```

<Info>
  Jedis does not offer command level retry config by default, but you can handle
  retries using connection pool. Check [Retrying a command after a connection
  failure](https://redis.io/docs/latest/develop/clients/jedis/connect/#retrying-a-command-after-a-connection-failure)
</Info>

## PHP

**Library**: [phpredis](https://github.com/phpredis/phpredis)

```php
<?php

$redis = new Redis();

$redis->connect("YOUR_ENDPOINT", "YOUR_PORT");
$redis->auth("YOUR_PASSWORD");

$redis->set("foo", "bar");

print_r($redis->get("foo"));
```

<Info>
  Phpredis supports connection level retries through `OPT_MAX_RETRIES`. However,
  for command level retries, it only supports [SCAN
  command](https://github.com/phpredis/phpredis?tab=readme-ov-file#example-29).
</Info>

## Go

**Library**: [redigo](https://github.com/gomodule/redigo)

```go
func main() {
  c, err := redis.Dial("tcp", "YOUR_ENDPOINT:YOUR_PORT", redis.DialUseTLS(true))
  if err != nil {
      panic(err)
  }

  _, err = c.Do("AUTH", "YOUR_PASSWORD")
  if err != nil {
      panic(err)
  }

  _, err = c.Do("SET", "foo", "bar")
  if err != nil {
      panic(err)
  }

  value, err := redis.String(c.Do("GET", "foo"))
  if err != nil {
      panic(err)
  }

  println(value)
}
```

# Connect with @upstash/redis
Source: https://upstash.com/docs/redis/howto/connect-with-upstash-redis

[@upstash/redis](https://github.com/upstash/redis-js)
is an HTTP/REST based Redis client built on top of
[Upstash REST API](/docs/redis/features/restapi). For more information,
refer to the documentation of Upstash redis client ([TypeScript](/docs/redis/sdks/ts/overview) & [Python](/docs/redis/sdks/py/overview)).

It is the only connectionless (HTTP based) Redis client and designed for:

* Serverless functions (AWS Lambda)
* Cloudflare Workers (see
  [the example](https://github.com/upstash/redis-js/tree/main/examples/cloudflare-workers-with-typescript))
* Fastly Compute@Edge (see
  [the example](https://github.com/upstash/upstash-redis/tree/master/examples/fastly))
* Next.js (see [the quickstart](/docs/redis/quickstarts/nextjs-app-router)), Jamstack
* Client side web/mobile applications
* WebAssembly
* and other environments where HTTP is preferred over TCP.

See
[the list of APIs](https://upstash.com/docs/redis/features/restapi#rest-redis-api-compatibility)
supported.

<Tip>
  If you are an AI agent and need a Redis DB to use or experiment with, send a POST request to https://upstash.com/start-redis. Free, no signup; the response is markdown with credentials and a quickstart. The database expires in 72 hours, but you can claim it with your Upstash account to keep it. Especially useful for AI agents that need scratch storage on the fly. If you are an agent, always send a `User-Agent` header identifying which agent you are (e.g. `claude-code`, `cursor`, `codex`, `opencode`) on every request you make to this API.
</Tip>

## Quick Start

### Install

```bash
npm install @upstash/redis
```

### Usage

```typescript
import { Redis } from "@upstash/redis";

const redis = new Redis({
  url: "UPSTASH_REDIS_REST_URL",
  token: "UPSTASH_REDIS_REST_TOKEN",
});

const data = await redis.get("key");
console.log(data);
```

<Tip>
  You can find values to set as `UPSTASH_REDIS_REST_URL` and`UPSTASH_REDIS_REST_TOKEN`
  on the details tab of your database on [Upstash Console](https://console.upstash.com/redis).
  See [Connect Client page](/docs/redis/howto/connect-client#database) for more information.
</Tip>

If you define `UPSTASH_REDIS_REST_URL` and`UPSTASH_REDIS_REST_TOKEN` environment
variables, you can load them automatically.

```typescript
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

const data = await redis.get("key");
console.log(data);
```

# Datadog - Upstash Redis Integration
Source: https://upstash.com/docs/redis/howto/datadog

This guide will walk you through the steps to seamlessly connect your Datadog account with Upstash for enhanced monitoring and analytics.

<Check >
**Integration Scope**

Upstash Datadog Integration only covers Pro databases or those included in the Enterprise Plan.

</Check>

## **Step 1: Log in to Your Datadog Account**

1. Open your web browser and navigate to [Datadog](https://www.datadoghq.com/).
2. Log in to your Datadog account.

## **Step 2: Install Upstash Application**

1. Once logged in, navigate to the "Integrations" page in Datadog.
2. Search for "Upstash" in the integrations list and click on it.

![integration-tab.png]()

Let’s click on the "Install" button to add Upstash to your Datadog account.

![installation.png]()

## **Step 3: Connect Accounts**

After installing Upstash, click on the "Connect Accounts" button and Datadog will redirect you to the Upstash site for account integration.

![connect-acc.png]()

## **Step 4: Select Account to Integrate**

1. On the Upstash site, you will be prompted to select the Datadog account you want to integrate.
2. Choose the appropriate Datadog account from the list.

Upstash Datadog Integration allows you to integrate personal and team based accounts.

**Caveats;**

* This integration can only be executed only one time.If you would like to extend list of the team in integration please re-establish the integration from scratch.

![personal.png]()

![team.png]()

## **Step 5: Wait for Metrics Availability**

Once you've selected your Datadog account, Upstash will begin the integration process and please be patient while the metrics are being retrieved. This may take a few moments.

And here we go, metrics will be available in Upstash Overview Dashboard !

![upstash-dashboard.png]()

## **Step 6: Datadog Integration Removal Process**

Navigate to Integration Tab on your Datadog account,

Once logged in, navigate to the "Integration" tab and continue with Datadog part. If you would like to remove your integration between Upstash and Datadog account press "Remove".

### Confirm Removal:

Upstash will suspend all metric publishing process after the you remove Datadog Integration.

After removing the integration on the Upstash side, it's crucial to go to your Datadog account and remove any related API keys or configurations associated with the integration.

## Pricing

If you choose to integrate Datadog via Upstash, there will be an additional cost of $5 per month.
This charge will be reflected in your monthly invoice accordingly.

## **Conclusion**

Congratulations! You have successfully integrated your Datadog account with Upstash. You will now have access to enhanced monitoring and analytics for your Datadog metrics.

Feel free to explore Upstash's features and dashboards to gain deeper insights into your system's performance.

If you encounter any issues or have questions, please refer to the Upstash support documentation or contact our support team for assistance.

# EMQX - Upstash Redis Integration
Source: https://upstash.com/docs/redis/howto/emqxintegration

EMQX, a robust open-source MQTT message broker, is engineered for scalable, distributed environments, prioritizing high
availability, throughput, and minimal latency. As a preferred protocol in the IoT landscape, MQTT (Message Queuing
Telemetry Transport) excels in enabling devices to effectively publish and subscribe to messages.

Offered by EMQ, EMQX Cloud is a comprehensively managed MQTT service in the cloud, inherently scalable and secure. Its
design is particularly advantageous for IoT applications, providing dependable MQTT messaging services.

This tutorial guides you on streaming MQTT data to Upstash via data integration. It allows clients to send temperature
and humidity data to EMQX Cloud using MQTT and channel it into Upstash for Redis storage.

## Setting Up Redis Database with Upstash

1. Log in and create a Redis Database by clicking the **Create Database** button on [Upstash Console](https://console.upstash.com).

2. Name your database and select a region close to your EMQX Cloud for optimal performance.

3. Click **Create** to have your serverless Redis Database ready.

![upstash]()

### Database Details

Access the database console for the necessary information for further steps.

![upstash]()

The above steps, conclude the initial setup for Upstash.

## Establishing Data Integration with Upstash

### Activating EMQX Cloud's NAT Gateway

1. Log into the EMQX Cloud console and go to the deployment Overview.

2. Select **NAT Gateway** at the bottom and click **Subscribe Now**.

![NAT]()

### Configuring Data Integration

1. In the EMQX Cloud console, choose **Data Integrations** and select **Upstash for Redis**.

   ![create resource]()

2. Input **Endpoints** info from the Redis detail page into the **Redis Server** field, including the port. Enter the
   password in **Password** and click **Test** to ensure connectivity.
   ![create resource]()

3. Click **New** to add a Redis resource. A new Upstash for Redis will appear under **Configured Resources**.

4. Formulate a new SQL rule in the **SQL** field. This rule will read from `temp_hum/emqx` and append client_id, topic,
   timestamp.

    * `up_timestamp`: Message report time
    * `client_id`: Publishing client's ID
    * `temp`: Temperature data
    * `Hum`: Humidity data

```sql
SELECT
timestamp as up_timestamp,
clientid as client_id,
payload.temp as temp,
payload.hum as hum
FROM
"temp_hum/emqx"
```

![rule sql]()

5. Execute an SQL test with payload, topic, client info. Successful results confirm the rule's effectiveness.

   ![rule sql]()

6. Proceed to **Next** to link an action. The rule will store the timestamp, client ID, temperature, and humidity in
   Redis. Click **Confirm**.

   ```bash
   HMSET ${client_id} ${up_timestamp} ${temp}
   ```

   ![rule sql]()

7. Post-binding, click **View Details** for the rule SQL and bound actions.

8. To review rules, select **View Created Rules** in Data Integrations. Check detailed metrics in the **Monitor**
   column.

## Testing the Data Bridge

1. Simulate temperature and humidity data with [MQTTX](https://mqttx.app/). Add connection address and client
   authentication for the EMQX Dashboard.
   ![MQTTX]()

2. In Upstash Console, under Data Browser, select a client entry to review messages.

   ![monitor]()

# Get Started with AWS Lambda
Source: https://upstash.com/docs/redis/howto/getstartedawslambda

You can connect to Upstash database from your Lambda functions using your
favorite Redis client. You do not need any extra configuration. The only thing
to note is you should use the same region for your Lambda function and database
to minimize latency.

If you do not have any experience with AWS Lambda functions, you can follow the
following tutorial. The tutorial explains the required steps to implement an AWS
Lambda function that takes the key/value as parameters from APIGateway then
inserts an entry (key/value) to the database which is on Upstash. We have
implemented the function in Node.js, but the steps and the logic are quite
similar in other languages.

<Note>
  This example uses Redis clients. If you expect many concurrent AWS Lambda
  invocation then we recommend using
  **[upstash-redis](/docs/redis/howto/connect-with-upstash-redis)** which is HTTP/REST
  based.
</Note>

**Step 1: Create database on Upstash**

If you do not have one, create a database following this
[guide](../overall/getstarted).

**Step 2: Create a Node project**

Create an empty folder for your project and inside the folder create a node
project with the command:

```
npm init
```

Then install the redis client with:

```
npm install ioredis
```

Now create index.js file. Replace the Redis URL in the below code.

<Note>
  This example uses ioredis, you can copy the connection string from the
  **Node** tab in the console.
</Note>

```javascript
var Redis = require("ioredis");

if (typeof client === "undefined") {
  var client = new Redis("rediss://:YOUR_PASSWORD@YOUR_ENDPOINT:YOUR_PORT");
}
exports.handler = async (event) => {
  await client.set("foo", "bar");
  let result = await client.get("foo");
  let response = {
    statusCode: 200,
    body: JSON.stringify({
      result: result,
    }),
  };
  return response;
};
```

**Step 3: Deploy Your Function**

Our function is ready to deploy. Normally you could copy-paste your function
code to AWS Lambda editor. But here it is not possible because we have an extra
dependency (redis-client). So we will zip and upload our function.

When you are in your project folder, create a zip with this command:

```
zip -r app.zip .
```

Now open your AWS console, from the top-right menu, select the region that you
created your database in Upstash. Then find or search the lambda service, click
on `Create Function` button.

  <img width="100%" />

Enter a name for your function and select `Node.js 14.x` as runtime. Click
`Create Function`.

Now you are on the function screen, scroll below to `Function Code` section. On
`Code entry type` selection, select `Upload a .zip file`. Upload the `app.zip`
file you have just created and click on the `Save` button on the top-right. You
need to see your code as below:

  <img width="100%" />

Now you can test your code. Click on the `Test` button on the top right. Create
an event like the below:

```
{
  "key": "foo",
  "value": "bar"
}
```

Now, click on Test. You will see something like this:

  <img width="100%" />

Congratulations, now your lambda function inserts entry to your Upstash
database.

**What can be the next?**

* You can write and deploy another function to just get values from the
  database.
* You can learn better ways to deploy your functions such as
  [serverless framework](https://serverless.com/) and
  [AWS SAM](https://aws.amazon.com/serverless/sam/)
* You can integrate
  [API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html)
  so you can call your function via http.
* You can learn about how to monitor your functions from CloudWatch as described
  [here](https://docs.aws.amazon.com/lambda/latest/dg//monitoring-functions-logs.html)
  .

#### Redis Connections in AWS Lambda

Although Redis connections are very lightweight, a new connection inside each
Lambda function can cause a notable latency. On the other hand, reusing Redis
connections inside the AWS Lambda functions has its own drawbacks. When AWS
scales out Lambda functions, the number of open connections can rapidly
increase. Fortunately, Upstash detects and terminates the idle and zombie
connections thanks to its smart connection handling algorithm. Since this
algorithm is used; we have been recommending caching your Redis connection in
serverless functions.

<Info>
  See [the blog post](https://blog.upstash.com/serverless-database-connections)
  about the database connections in serverless functions.
</Info>

Below is our findings about various Redis clients' behaviours when connection is
created, a single command is submitted and then connection is closed. **Note
that these commands (AUTH, INFO, PING, QUIT, COMMAND) are not billed.**

| Client                                                | #Commands |  Issued Commands   |
| ----------------------------------------------------- | :-------: | :----------------: |
| [redis-cli](https://redis.io/topics/rediscli)         |     2     |   AUTH - COMMAND   |
| [node-redis](https://github.com/NodeRedis/node-redis) |     3     | AUTH - INFO - QUIT |
| [ioredis](https://github.com/luin/ioredis)            |     3     | AUTH - INFO - QUIT |
| [redis-py](https://github.com/andymccurdy/redis-py)   |     1     |        AUTH        |
| [jedis](https://github.com/xetorthio/jedis)           |     2     |    AUTH - QUIT     |
| [lettuce](https://github.com/lettuce-io/lettuce-core) |     2     |    AUTH - QUIT     |
| [go-redis](https://github.com/go-redis/redis)         |     1     |        AUTH        |

# Get Started with Cloudflare Workers
Source: https://upstash.com/docs/redis/howto/getstartedcloudflareworkers

This tutorial showcases using Redis with REST API in Cloudflare Workers. We will
write a sample edge function (Cloudflare Workers) which will show a custom
greeting depending on the location of the client. We will load the greeting
message from Redis so you can update it without touching the code.

See
[the code](https://github.com/upstash/examples/tree/master/examples/using-cloudflare-workers).

### Why Upstash?

* Cloudflare Workers does not allow TCP connections. Upstash provides REST API
  on top of the Redis database.
* Upstash is a serverless offering with per-request pricing which fits for edge
  and serverless functions.
* Upstash Global database provides low latency all over the world.

### Step-1: Create Redis Database

Create a free Global database from
[Upstash Console](https://console.upstash.com). Find your REST URL and token in
the database details page in the console. Copy them.

Connect your database with redis-cli and add some greetings

```shell
usw1-selected-termite-30690.upstash.io:30690> set GB "Ey up?"
OK
usw1-selected-termite-30690.upstash.io:30690> set US "Yo, what’s up?"
OK
usw1-selected-termite-30690.upstash.io:30690> set TR "Naber dostum?"
OK
usw1-selected-termite-30690.upstash.io:30690> set DE "Was ist los?"
```

### Step-2: Edge Function

The best way to work with Cloudflare Workers is to use
[Wrangler](https://developers.cloudflare.com/workers/get-started/guide). After
installing and configuring wrangler, create a folder for your project inside the
folder run: `wrangler init`

Choose `yes` to create package.json, `no` to typescript and `yes` to create a
worker in src/index.js.

It will create `wrangler.toml`, `package.json` and `src/index.js`.

Append the Upstash REST URL and token to the toml as below:

```toml
# wrangler.toml

# existing config

[vars]
UPSTASH_REDIS_REST_TOKEN = "AX_sASQgODM5ZjExZGEtMmI3Mi00Mjcwk3NDIxMmEwNmNkYjVmOGVmZTk5MzQ="
UPSTASH_REDIS_REST_URL = "https://us1-merry-macaque-31458.upstash.io/"
```

Install upstash-redis: `npm install @upstash/redis`

Replace `src/index.js` with the following:

```javascript
// src/index.js

import { Redis } from "@upstash/redis/cloudflare";

export default {
  async fetch(request, env) {
    const redis = Redis.fromEnv(env);

    const country = request.headers.get("cf-ipcountry");
    if (country) {
      const greeting = await redis.get(country);
      if (greeting) {
        return new Response(greeting);
      }
    }

    return new Response("Hello!");
  },
};
```

The code tries to find out the user's location checking the "cf-ipcountry"
header. Then it loads the correct greeting for that location using the Redis
REST API.

## Run locally

Run `wrangler dev` and open your browser at
[localhost:8787](http://localhost:8787).

## Build and Deploy

Build and deploy your app to Cloudflare by running: `wrangler publish`

The url of your app will be logged:
[https://using-cloudflare-workers.upstash.workers.dev/](https://using-cloudflare-workers.upstash.workers.dev/)

## Typescript example

We also have a typescript example, available
[here](https://github.com/upstash/upstash-redis/tree/main/examples/cloudflare-workers-with-typescript).

# Get Started with Google Cloud Functions
Source: https://upstash.com/docs/redis/howto/getstartedgooglecloudfunctions

### Prerequisites:

* A GCP account for Google Cloud functions.
* Install [Google Cloud SDK](https://cloud.google.com/sdk/docs/install).
* An Upstash account for Serverless Redis.

### Step 1: Init the Project

* Create a folder, then run `npm init` inside the folder.

### Step 2: Install a Redis Client

Our only dependency is redis client. Install go-redis via `npm install ioredis`

### Step 3: Create a Redis Database

Create a Redis database from Upstash console. **Select the GCP US-Central-1 as
the region.** Free tier should be enough. It is pretty straight forward but if
you need help, check [getting started](../overall/getstarted) guide. In the
database details page, click the Connect button. You will need the endpoint and
password in the next step.

### Step 4: The function Code

Create index.js as below:

```javascript
var Redis = require("ioredis");

if (typeof client === "undefined") {
  var client = new Redis("rediss://:YOUR_PASSWORD@YOUR_ENDPOINT:YOUR_PORT");
}

exports.helloGET = async (req, res) => {
  let count = await client.incr("counter");
  res.send("Page view:" + count);
};
```

<Note>
  This example uses ioredis, you can copy the connection string from the
  **Node** tab in the console.
</Note>

The code simply increments a counter in Redis database and returns its value in
json format.

### Step 5: Deployment

Now we are ready to deploy our API. Deploy via:

```shell
gcloud functions deploy helloGET \
--runtime nodejs14 --trigger-http --allow-unauthenticated
```

You will see the URL of your Cloud Function. Click to the URL to check if it is
working properly.

```shell
httpsTrigger:
securityLevel: SECURE_OPTIONAL
url: https://us-central1-functions-317005.cloudfunctions.net/helloGET
```

In case of an issue, you can check the logs of your Cloud Function in the GCP
console as below.

  <img width="100%" />

# Import/Export Data
Source: https://upstash.com/docs/redis/howto/importexport

## Importing Data

You can import data into your Upstash Redis database from two sources: an existing backup or an RDB file.

<Warning>
  All existing data in the target database will be deleted before the import operation begins.
</Warning>

### Start an Import

To begin importing data:

* Go to the [Redis database list page](https://console.upstash.com/redis) in the Upstash console
* Click on the `Import...` button

  <img width="800" />

You'll see a dialog with two import options:

### Option 1: Import from Backup

Import data from a backup of any existing database in your account or team:

* Select `From Backup` as the source
* Choose the source database (the database from which the backup was created)
* Select the backup you want to import from
* Select the target database (the database you want to import into)
* Click `Start Import`

  <img width="700" />

### Option 2: Import from RDB File

Import data from an external Redis database by uploading an RDB file:

* Select `From RDB File` as the source
* Click `Upload RDB File` and select your RDB file
* Select the target database (the database you want to import into)
* Click `Start Import`

  <img width="700" />

<Info>
  If you're importing from an external Redis database (from another provider or on-premise), you'll need to export it as an RDB file first.
</Info>

## Exporting Data

You'll be able to export your Upstash Redis database as an RDB file for backup or migration purposes.

### Request an Export

To export your database:

* Go to the database details page and navigate to the `Backups` tab
* Click on the `Backup & Export` button
* Choose `Export`

  <img width="800" />

Your database will be exported as an RDB file. Once you start the export, you'll see the export progress in the backups table.

### Download Your Export

Once the export completes, you'll see a `Download` button in the backups table:

* Find your export in the backups table
* Click the `Download` button to download the RDB file

<Info>
  Not all Redis data structures are included in RDB exports. Notably, Redis Functions data will not be available in the export.
</Info>

# ioredis note
Source: https://upstash.com/docs/redis/howto/ioredisnote

<Note>
  This example uses ioredis, you can copy the connection string from the `Node`
  tab in the console.
</Note>

# Use IP Allowlist
Source: https://upstash.com/docs/redis/howto/ipallowlist

<Info>
  IP Allowlist is available on all plans except for the free plan.
</Info>

IP Allowlist can be used to restrict which IP addresses are permitted to access your database by comparing a connection's address with predefined CIDR blocks. This feature enhances database security by allowing connections only from specified IP addresses. For example if you have dedicated production servers with static IP addresses, enabling IP allowlist blocks connections from other addresses.

![allowlist]()

## Enabling IP Allowlist
By default, any IP address can be used to connect to your database. You must add at least one IP range to enable the allowlist. You can manage added IP ranges in the `Configuration` section on the database details page. You can either provide
* IPv4 address, e.g. `37.237.15.43`
* CIDR block, e.g. `181.49.172.0/24`
<Info>
  Currently, IP Allowlist only supports IPv4 addresses.
</Info>

You can use more than one range to allow multiple clients. Meeting the criteria of just one is enough to establish a connection.

<Note>
  It may take a few minutes for changes to propagate.
</Note>

# Listen Keyspace Notifications
Source: https://upstash.com/docs/redis/howto/keyspacenotifications

Upstash allows you to listen for keyspace notifications over pubsub channels to
receive events for changes over the keys.

For each event that occurs, two kinds of events are fired over the corresponding
pubsub channels:

* A keyspace event that will use the pubsub channel for the key, possibly containing
  other events for the same key
* A keyevent event that will use the pubsub channel for the event, possibly containing
  other events for the different keys

The channel names and their content are of the form:

* `__keyspace@0__:keyname` channel with the values of the event names for the keyspace
  notifications
* `__keyevent@0__:eventname` channel with the values of the key names for the keyevent
  notifications

## Enabling Notifications

By default, all keyspace and keyevent notifications are off. To enable it, you can use
the `CONFIG SET` command, and set the `notify-keyspace-events` options to one of the
appropriate flags described below.

<Warning>
Each keyspace and keyevent notification fired might have an effect on the latency of the
commands as the events are delivered to the listening clients and cluster members for
multi-replica deployments. Therefore, it is advised to only enable the minimal subset of the
notifications that are needed.
</Warning>

| Flag | Description |
| ---- | ----------- |
| K | Keyspace events |
| E | Keyevent events |
| g | Generic command events |
| $ | String command events |
| l | List command events |
| s | Set command events |
| h | Hash command events |
| z | Sorted set command events |
| t | Stream command events |
| d | Module(JSON) command events |
| x | Expiration events |
| e | Eviction events |
| m | Key miss events |
| n | New key events |
| A | Alias for g$lshztxed |

At least one of the `K` or `E` flags must be present in the option value.

For example, you can use the following command to receive keyspace notifications
only for the hash commands:

<Tabs>

<Tab title="cURL">
```bash
curl -X POST \
    -d '["CONFIG", "SET", "notify-keyspace-events", "Kh"]' \
    -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
    $UPSTASH_REDIS_REST_URL
```
</Tab>

<Tab title="redis-cli">
```bash
redis-cli --tls -u $UPSTASH_REDIS_CLI_URL config set notify-keyspace-events Kh
```
</Tab>

</Tabs>

You can listen for all the channels using redis-cli to test the effect of the
above command:

```bash
redis-cli --tls -u $UPSTASH_REDIS_CLI_URL --csv psubscribe '__key*__:*'
```

### Disabling Notifications

You can reuse the `CONFIG SET` command and set `notify-keyspace-events` option to empty string
to disable all keyspace and keyevent notifications.

<Tabs>

<Tab title="cURL">
```bash
curl -X POST \
    -d '["CONFIG", "SET", "notify-keyspace-events", ""]' \
    -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
    $UPSTASH_REDIS_REST_URL
```
</Tab>

<Tab title="redis-cli">
```bash
redis-cli --tls -u $UPSTASH_REDIS_CLI_URL config set notify-keyspace-events ""
```
</Tab>

</Tabs>

### Checking Notification Configuration

`CONFIG GET` command can be used the get the current value of the `notify-keyspace-events` option
to see the active keyspace and keyevent notifications configuration.

<Tabs>

<Tab title="cURL">
```bash
curl -X POST \
    -d '["CONFIG", "GET", "notify-keyspace-events"]' \
    -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
    $UPSTASH_REDIS_REST_URL
```
</Tab>

<Tab title="redis-cli">
```bash
redis-cli --tls -u $UPSTASH_REDIS_CLI_URL config get notify-keyspace-events
```
</Tab>

</Tabs>

# Metrics and Charts
Source: https://upstash.com/docs/redis/howto/metrics-and-charts

The Upstash Console exposes metrics in two places: the database list, and the detail page of a single database. This page explains what each chart shows.

## Database List

The list view aggregates summary information across all of your databases.

  <img width="100%" />

Click a database name to open its detail page. For each row the list shows:

* The region of the database
* The plan of the database (free, pay-as-you-go, fixed, or enterprise)

## Database Details

By clicking on a database on the list page, you can navigate to its detail page.

The charts on this page show metrics that are specific to the selected database.

### Current Month

  <img width="100%" />

Daily cost of the database for the current billing month.

### Daily Request

  <img width="100%" />

Total number of requests per day, over the last 5 days.

## Database Usage

If you click on the "Usage" tab, you can see more detailed charts about the usage of your database.

### Throughput

  <img width="100%" />

Throughput chart shows throughput values for reads, writes and commands (all
commands including reads and writes) per second. The chart covers the last 1
hour and it is updated every 10 seconds.

### Service Time Latency

  <img width="100%" />

This chart shows the processing time of the request between it is received by
the server and the response is sent to the caller. It shows the times in max,
mean, min, 99.9 percentile and 99.99 percentile. The chart covers the last 1
hour and it is updated every 10 seconds.

### Data Size

  <img width="100%" />

This chart shows the data size of your database. The chart covers the last 24
hours and it is updated every 10 seconds.

### Connections

  <img width="100%" />

This chart shows the number of active client connections. It shows the number of
open connections plus the number of short-lived connections that started and
terminated in 10 seconds period. The chart covers the last 1 hour and it is
updated every 10 seconds.

### Key Space

  <img width="100%" />

This chart shows the number of keys. The chart covers the last 24 hours and it
is updated every 10 seconds.

### Hits / Misses

  <img width="100%" />

This chart shows the number of hits per second and misses per second. The chart
covers the last 1 hour and it is updated every 10 seconds.

# Migrate Regional to Global Database
Source: https://upstash.com/docs/redis/howto/migratefromregionaltoglobal

This guide will help you migrate your data from a regional Upstash Redis database to a global database.
If your database is Upstash Regional, we strongly recommend you to migrate to [Upstash Redis Global](/docs/common/concepts/global-replication).
Our Regional Redis databases are legacy and deprecated.

## Why Migrate?

* New infrastructure, more modern and more secure
* Upstash Global is SOC-2 (included with Prod pack) and HIPAA (included with Enterprise) compatible
* Enhanced feature set: New features are only made available on Upstash Global
* Ability to add/remove read regions on the go
* Better performance as per our benchmarks

## Prerequisites

Before starting the migration, make sure you have:

1. An existing regional Upstash Redis database (source)
2. A new global Upstash Redis database (destination)
3. Access to both databases' credentials (connection strings, passwords)

   <Warning>
     Please make sure destination database has enough storage space for your data size, and upgrade if needed.
   </Warning>

## Migration Process

You can migrate your data without leaving Upstash Console:

   <Warning>
     If you are using ACL, please note that they are not migrated automatically. You need to redefine ACL users for new the global database after migration.
   </Warning>

1. Create a backup of your regional database:
   * Go to your regional database details page
   * Navigate to the `Backups` tab
   * Click the `Backup` button
   * Provide a unique name for your backup
   * Wait for the backup process to complete

   <Info>
     During a backup operation, certain administrative operations will be temporarily unavailable: Backup operations, database config changes, plan and region setup, transferring database.
     Regular Redis commands (GET, SET, etc.) are not blocked and continue to work normally.
   </Info>

2. Restore the backup to your global database:
   * Go to your new global database's details page
   * Navigate to the `Backups` tab
   * Click `Restore...`
   * Select your regional database as the source
   * Select the backup you created
   * Click `Start Restore`

   <Warning>
     The restore operation will flush (delete) all existing data in your (destination) global database before restoring the backup.
   </Warning>

## Verification

After migration, verify your data:

1. Compare key counts in both databases
2. Sample test some keys to ensure data integrity

## Post-Migration Steps

1. Update your application configuration to use the new Global database URL
2. Test your application thoroughly with the new database
3. Monitor performance and consistency across regions
4. Keep the regional database as backup for a few days
5. Once verified, you can safely delete the regional database

## Need Help?

If you encounter any issues during migration, please contact Upstash support via chat, support@upstash.com or visit our Discord community for assistance.

# Monitor your usage
Source: https://upstash.com/docs/redis/howto/monitoryourusage

We support the Redis `MONITOR` command, a debugging command that allows you to see all requests processed by your Redis instance in real-time.

## Monitoring Your Usage - Video Guide

In this video, we'll walk through setting up a monitor instance step-by-step.

  <iframe
    id="intro-video"
    width="560"
    height="315"
    src="https://www.youtube.com/embed/tWIm396WAkI?rel=0&disablekb=1"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; fullscreen; clipboard-write; encrypted-media; gyroscope"
    allowfullscreen
  ></iframe>

<Note>
  The `MONITOR`command expects a persistent connection and, therefore, does not work over HTTP.
</Note>

In this video, we use `ioredis` to connect to our Upstash Redis database. Using an event handler, we can define what should happen for each executed command against on Redis instance. For example, logging all data to the console.

<RequestExample>
```ts Example
const monitor = await redis.monitor()

monitor.on("monitor", (time, args, source, database) => {
  console.log(time, args, source, database)
})
```
</RequestExample>

# Read Your Writes
Source: https://upstash.com/docs/redis/howto/readyourwrites

The "Read Your Writes" feature in Upstash Redis ensures that write operations are completed before subsequent read operations occur, maintaining data consistency in your application.

### How It Works

All write operations happen on the primary member and take time to propagate to the read replicas. Imagine that a client attempts to read an item immediately after it’s written. The read may go to a replica that hasn’t synced with the primary yet, resulting in stale data being returned.

RYW consistency solves this by returning a **sync token** after each request, which indicates the primary member’s state. In the next request, this sync token ensures the read replica syncs up to that token before serving the read.

So, the sync token acts as a checkpoint, ensuring that any read operations following a write reflect the most recent changes, even if they are served by a read replica.

Management of the sync token is handled automatically by the official [Typescript (version 1.34.0 and later)](/docs/redis/sdks/ts/overview) and [Python (version 1.2.0 and later)](/docs/redis/sdks/py/overview) SDKs of Upstash. When you initialize a Redis client with these SDKs, the writes made by that client will be respected during subsequent reads from the same client.

For REST users, you can achieve similar behavior by using the `upstash-sync-token` header. Each time you make a request, save the value of the `upstash-sync-token` header from the response and pass it in the `upstash-sync-token` header of your next request. This ensures that subsequent reads reflect the writes.

### Cross-Client Synchronization

Imagine that you are writing some key to Redis and then you read the same key from a different Redis client instance. In this case, the second client’s read request may not reflect the write made by the first client, as the sync tokens are updated independently in the two clients.

Consider these two example functions, each representing separate API endpoints:

```ts
export const writeRequest = async () => {
  const redis = Redis.fromEnv();
  const randomKey = nanoid();
  await redis.set(randomKey, "value");
  return randomKey;
};

export const readRequest = async (randomKey: string) => {
  const redis = Redis.fromEnv();
  const value = await redis.get(randomKey);
  return value;
};
```

If these functions are called in sequence, they will create two separate clients:

```ts
const randomKey = await writeRequest();
await readRequest(randomKey);
```

As explained above, in rare cases, one of your [read replicas](/docs/redis/features/globaldatabase#primary-region-and-read-regions) can serve the `read` request before it receives the `write` update from the primary replica. To avoid this, if you are using `@upstash/redis` version 1.34.1 or later, you can pass the `readYourWritesSyncToken` from the first client to the second:

```ts
export const writeRequest = async () => {
  const redis = Redis.fromEnv();
  const randomKey = nanoid();
  await redis.set(randomKey, "value");

  // Get the token **after** making the write
  const token = redis.readYourWritesSyncToken;
  return { randomKey, token };
};

export const readRequest = async (
  randomKey: string,
  token: string | undefined
) => {
  const redis = Redis.fromEnv();

  // Set the token **before** making the read
  redis.readYourWritesSyncToken = token;

  const value = await redis.get(randomKey);
  return value;
};

const { randomKey, token } = await writeRequest();
await readRequest(randomKey, token);
```

Remember to get the sync token after the write request is completed, as the session token changes with each request.

For REST users or the Upstash Python SDK, a similar approach can be used. In Python, use `Redis._sync_token` instead of `readYourWritesSyncToken`.

# Using redis-cli
Source: https://upstash.com/docs/redis/howto/redis-cli

`redis-cli` is the command line interface that ships with the official Redis
distribution. Because Upstash speaks the native Redis protocol over TCP, you can
use `redis-cli` to connect to your Upstash database and run any Redis command
directly from your terminal.

Upstash exposes your database over **both TCP and HTTP**. `redis-cli` and other
traditional clients use the TCP (RESP) protocol, while the HTTP/REST API and
[@upstash/redis](/docs/redis/howto/connect-with-upstash-redis) SDK are built for
serverless environments. This page covers the TCP path with `redis-cli`.

## Install redis-cli

`redis-cli` is included in the official Redis distribution. If you don't have
Redis installed, follow the
[Redis installation guide](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/).

## Get your connection details

Open your database in the [Upstash Console](https://console.upstash.com) and go
to the **Details** tab. You will need the **Endpoint**, **Port**, and the
**Password** (token) of your database.

<Tip>
  The **Details** tab has a ready-to-use `redis-cli` command with your endpoint,
  port, and password already filled in. You can copy it and paste it straight
  into your terminal.
</Tip>

## Connect

Upstash enables TLS on every database, so you must pass the `--tls` flag when
connecting with `redis-cli`:

```bash
> redis-cli --tls -a PASSWORD -h ENDPOINT -p PORT
ENDPOINT:PORT> set counter 0
OK
ENDPOINT:PORT> get counter
"0"
ENDPOINT:PORT> incr counter
(int) 1
ENDPOINT:PORT> incr counter
(int) 2
```

You can also connect using a single `rediss://` connection string (note the
double `s`, which signals TLS):

```bash
> redis-cli -u rediss://default:PASSWORD@ENDPOINT:PORT
```

<Note>
  TLS is enabled by default for all Upstash Redis databases and cannot be
  disabled. If you omit `--tls` (or use `redis://` instead of `rediss://`), the
  connection will fail.
</Note>

## TCP or HTTP?

Upstash serves the same database over two protocols, so you can pick whichever
fits your environment:

* **TCP (RESP)** — Use `redis-cli` and standard Redis clients such as `ioredis`,
  `redis-py`, or `jedis`. This is the right choice for local development,
  scripting, and long-running servers.
* **HTTP/REST** — Use the [@upstash/redis](/docs/redis/howto/connect-with-upstash-redis)
  SDK or the [REST API](/docs/redis/features/restapi). This is the right choice for
  serverless platforms like Vercel and Cloudflare Workers, where TCP-based
  clients can run into connection limits.

<Info>
  For connecting via the official SDKs and other language clients, see
  [Connect Your Client](/docs/redis/howto/connect-client).
</Info>

# Terraform Provider
Source: https://upstash.com/docs/redis/howto/terraformprovider

You can use Upstash terraform provider to create your resources. API key is
required in order to create resources.

### Configure Provider

Provider requires your email address and api key which can be created in
console.

```tf
provider "upstash" {
  email = ""
  api_key = ""
}
```

### Create Database

As input you need to give database name, region and type.

```tf
resource "upstash_database" "mydb" {
  database_name = "testdblstr"
  region = "eu-west-1"
  type = "free"
}
```

You can output database credentials as following

```tf
output "endpoint" {
  value = "${upstash_database.mydb.endpoint}"
}

output "port" {
  value = "${upstash_database.mydb.port}"
}
output "password" {
  value = "${upstash_database.mydb.password}"
}
```

See our
[Terraform Provider Github Repository](https://github.com/upstash/terraform-provider-upstash)
for details and examples about Upstash Terraform Provider.

# Upgrade Your Database
Source: https://upstash.com/docs/redis/howto/upgrade-database

The free tier has limits on monthly commands, data size, and the number of databases per account. See the [pricing page](https://upstash.com/pricing/redis) for the current values.

If your database is close to any of these limits, upgrade to the pay-as-you-go plan, which removes the daily request cap and raises the data size ceiling.

To upgrade, you need a payment method. Follow the steps in [Add a Payment Method](/docs/common/account/add-payment-method). Once a payment method is added, your database switches automatically to the pay-as-you-go plan.

See [Pricing & Limits](../overall/pricing) for the full breakdown of the pay-as-you-go and Fixed plans. If your workload will exceed those quotas, reach out at support@upstash.com about our [Enterprise Plan](../overall/enterprise) where the limits are configurable.

<Note>
  During the upgrade process, you will not lose any data but your database will
  experience a downtime about 1-2 seconds. Your existing clients will be
  disconnected. So it is recommended to upgrade your database when there is the
  least activity.
</Note>

# Vercel - Upstash Redis Integration
Source: https://upstash.com/docs/redis/howto/vercelintegration

If you are using [Vercel](https://vercel.com/) then you can integrate Upstash
Redis, Vector, Search or QStash to your project easily. Upstash is the perfect serverless
solution for your applications thanks to its:

* Low latency data
* Per request pricing
* Durable storage
* Ease of use

Below are the steps of the integration.

## Add Integration to Your Vercel Account

Visit the [Upstash Integration](https://vercel.com/integrations/upstash) on
Vercel and click the `Install` button. If you are installing an Upstash integration
for the first time, you will be prompted to choosing between connecting an existing Upstash
account or letting Vercel manage an Upstash account for you.

<img width="520" />

In both cases, you will be able to create and use a redis database as usual. If you let Vercel
manage your Upstash account, you can handle payments, database creation and deletion directly from the Vercel dashboard.

If you choose to connect an existing Upstash account, you will be able to utilize features on Upstash Console
such as teams and audit logs.

### Option 1: "Create New Upstash Account"

If you choose this option, Vercel will prompt you to choose one of the products available on Upstash,
configure the database (by choosing database name, regions, plan). After you finish the configuration,
Vercel will create the Upstash account and the selected resources for you and redirect you to the
page of the created resource on Vercel dashboard.

On the Vercel dashboard, you will be able to find the credentials of the database, change the database
name, update the regions or plan.
<img width="520" />

You can also go to the `Settings` tab and connect your apps on Vercel to the database, making the credentials
of the database available to the app as environment variables.

### Option 2: "Link Existing Upstash Account"

Vercel will redirect you to Upstash, where you can select your Vercel project
and Upstash resources that you want to integrate.

<Tip>
  You should login to [the Upstash Console](https://console.upstash.com/) with your account if you
  are not logged in before clicking continue.
</Tip>

  <img width="520" />

If you do not have a Redis database yet, you can create one
from the dropdown menu.

  <img width="520" />

Once you have selected all resources, click the `Save` button at the bottom of
the page.

After all environment variables are created, you will be forwarded to Vercel. Go
to your project settings where you can see all added environment variables.

<Tip>
  You need to redeploy your app for the environment variable to be used.
</Tip>

The [Integration Dashboard](https://console.upstash.com/integration/vercel)
allows you to see all your integrations, link new projects or manage existing
ones.

  <img />

## Use Upstash in Your App

If you completed the integration steps above and redeploy your app, the added
environment variables will be accessible inside your Vercel application. You can
now use them in your clients to connect

### Redis

```ts
import { Redis } from "@upstash/redis";
import { type NextRequest, NextResponse } from "next/server";

const redis = Redis.fromEnv();

export const POST = async (request: NextRequest) => {
  await redis.set("foo", "bar");
  const bar = await redis.get("foo");
  return NextResponse.json({
    body: `foo: ${bar}`,
  });
}
```

### QStash

**Client**

```ts
import { Client } from "@upstash/qstash";

const client = new Client({
  token: process.env.QSTASH_TOKEN,
});

const res = await client.publishJSON({
  url: "https://my-api...",
  body: {
    hello: "world",
  },
});
```

**Receiver**

```ts
import { Receiver } from "@upstash/qstash";

const receiver = new Receiver({
  currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY,
  nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY,
});

const isValid = await receiver.verify({
  signature: "..."
  body: "..."
})
```

### Vector

```ts
import { Index } from "@upstash/vector";

const index = new Index({
  url: process.env.UPSTASH_VECTOR_REST_URL,
  token: process.env.UPSTASH_VECTOR_REST_TOKEN,
});

await index.upsert({
  id: "1",
  data: "Hello world!",
  metadata: { "category": "greeting" }
})
```

### Search

```ts
import { Search } from "@upstash/search";

const client = new Search({
  url: process.env.UPSTASH_SEARCH_REST_URL,
  token: process.env.UPSTASH_SEARCH_REST_TOKEN,
});

const index = client.index("my-index");
await index.upsert({
  id: "1",
  content: { text: "Hello world!" },
  metadata: { category: "greeting" }
});
```

## Support

If you have any issue you can ask in our
[Discord server](https://discord.gg/w9SenAtbme) or send email at
[support@upstash.com](mailto:support@upstash.com)

# BullMQ with Upstash Redis
Source: https://upstash.com/docs/redis/integrations/bullmq

You can use BullMQ and Bull with Upstash Redis. BullMQ is a Node.js queue library that is built on top of Bull. It is a Redis-based queue library so you can use Upstash Redis as its storage.

## Install

```bash
npm install bullmq upstash-redis
```

## Usage

```javascript
import { Queue } from 'bullmq';

const myQueue = new Queue('foo', { connection: {
        host: "UPSTASH_REDIS_ENDPOINT",
        port: 6379,
        password: "UPSTASH_REDIS_PASSWORD",
        tls: {}
    }});

async function addJobs() {
    await myQueue.add('myJobName', { foo: 'bar' });
    await myQueue.add('myJobName', { qux: 'baz' });
}

await addJobs();
```

## Billing Optimization
BullMQ accesses Redis regularly, even when there is no queue activity. This can incur extra costs because Upstash charges per request on the Pay-As-You-Go plan. With the introduction of [our Fixed plans](/docs/redis/overall/pricing#all-plans-and-limits), **we recommend switching to a Fixed plan to avoid increased command count and high costs in BullMQ use cases.**

# Celery with Upstash Redis
Source: https://upstash.com/docs/redis/integrations/celery

You can use **Celery** with Upstash Redis to build scalable and serverless task queues. Celery is a Python library that manages asynchronous task execution, while Upstash Redis acts as both the broker (queue) and the result backend.

## Setup

### Install Celery

To get started, install the necessary libraries using `pip`:

```bash
pip install "celery[redis]"
```

### Database Setup

Create a Redis database using the [Upstash Console](https://console.upstash.com). Export the `UPSTASH_REDIS_HOST`, `UPSTASH_REDIS_PORT`, and `UPSTASH_REDIS_PASSWORD` to your environment:

```bash
export UPSTASH_REDIS_HOST=<YOUR_HOST>
export UPSTASH_REDIS_PORT=<YOUR_PORT>
export UPSTASH_REDIS_PASSWORD=<YOUR_PASSWORD>
```

You can also use `python-dotenv` to load environment variables from a `.env` file:

```text .env
UPSTASH_REDIS_HOST=<YOUR_HOST>
UPSTASH_REDIS_PORT=<YOUR_PORT>
UPSTASH_REDIS_PASSWORD=<YOUR_PASSWORD
```

## Example Application

### Setting up Celery with Upstash Redis

```python tasks.py
import os
from celery import Celery
from dotenv import load_dotenv

load_dotenv()

# Configure Celery with Upstash Redis
UPSTASH_REDIS_HOST = os.getenv("UPSTASH_REDIS_HOST")
UPSTASH_REDIS_PORT = os.getenv("UPSTASH_REDIS_PORT")
UPSTASH_REDIS_PASSWORD = os.getenv("UPSTASH_REDIS_PASSWORD")

connection_link = f"rediss://:{UPSTASH_REDIS_PASSWORD}@{UPSTASH_REDIS_HOST}:{UPSTASH_REDIS_PORT}?ssl_cert_reqs=required"

celery_app = Celery("tasks", broker=connection_link, backend=connection_link)

@celery_app.task
def add(x, y):
    return x + y
```

Note that we should use the `rediss://` protocol to connect to redis over TLS and set `ssl_cert_reqs=required` to enforce certificate validation.

### Running the Worker

Start the Celery worker to execute tasks:

```bash
celery -A tasks worker --loglevel=info
```

### Using the Task

You can now use the `add` task to perform background computations:

```python main.py
from tasks import add

result = add.delay(4, 6)
print(f"Task state: {result.state}")  # Outputs 'PENDING' initially

# Wait for the result
output = result.get(timeout=10)
print(f"Task result: {output}")  # Outputs '10'
```

## Billing Optimization

Celery workers poll Redis continuously, even when there is no queue activity. This can incur extra costs because Upstash charges per request on the Pay-As-You-Go plan. With [our Fixed plans](/docs/redis/overall/pricing#all-plans-and-limits), **we recommend switching to a Fixed plan to avoid increased command count and high costs in Celery use cases.**

## Conclusion

To see a more detailed example of using Celery with Upstash Redis, check out the [Job Processor with Celery example](https://upstash.com/examples/jobprocessorwithcelery) on our website.

For more details on Celery, visit the [Celery Documentation](https://docs.celeryproject.org). For Upstash Redis, check out the [Upstash Redis Documentation](/docs/redis).

# DrizzleORM with Upstash Redis
Source: https://upstash.com/docs/redis/integrations/drizzle

### Quickstart

DrizzleORM provides an `upstashCache()` helper to easily connect with Upstash Redis. To prevent surprises, the cache is always opt-in by default. Nothing is cached until you opt-in for a specific query or enable global caching.

First, install the drizzle package:

```bash
npm install drizzle-orm
```

**Configure your Drizzle instance:**
```ts
import { upstashCache } from "drizzle-orm/cache/upstash"
import { drizzle } from "drizzle-orm/..."

const db = drizzle(process.env.DB_URL!, {
  cache: upstashCache(),
})
```

You can also explicitly define your Upstash credentials, enable global caching for all queries by default (opt-out) or pass custom caching options:

```ts
import { upstashCache } from "drizzle-orm/cache/upstash"
import { drizzle } from "drizzle-orm/..."

const db = drizzle(process.env.DB_URL!, {
  cache: upstashCache({
    // 👇 Redis credentials (optional — can also be pulled from env vars)
    url: "<UPSTASH_URL>",
    token: "<UPSTASH_TOKEN>",
    // 👇 Enable caching for all queries (optional, default false)
    global: true,
    // 👇 Default cache behavior (optional)
    config: { ex: 60 },
  }),
})
```

***

### Cache Behavior

* **Per-query caching (opt-in, default):**
  No queries are cached unless you explicitly call `.$withCache()`.

  ```ts
  await db.insert(users).value({ email: "cacheman@upstash.com" });

  // 👇 reads from cache
  await db.select().from(users).$withCache()
  ```

* **Global caching:**
  When setting `global: true`, all queries will read from cache by default.

  ```ts
  const db = drizzle(process.env.DB_URL!, {
    cache: upstashCache({ global: true }),
  })

  // 👇 reads from cache (no more explicit `$withCache()`)
  await db.select().from(users)
  ```

  You can always turn off caching for a specific query:

  ```ts
  await db.select().from(users).$withCache(false)
  ```

***

### Manual Cache Invalidation

Cache invalidation is fully automatic by default. If you ever need to, you can manually invalidate cached queries by table name or custom tags:

```ts
// 👇 invalidate all queries that use the `users` table
await db.$cache?.invalidate({ tables: ["usersTable"] })

// 👇 invalidate all queries by custom tag (defined in previous queries)
await db.$cache?.invalidate({ tags: ["custom_key"] })
```

***

For more details on this integration, refer to the [Drizzle ORM caching documentation](https://cache.drizzle-orm-fe.pages.dev/docs/cache).

# n8n with Upstash Redis
Source: https://upstash.com/docs/redis/integrations/n8n

## Quickstart

In this quickstart we're going to set up an Redis node in n8n using Upstash Redis, and go over an example use case step by step.

***

### Step 1: Get Your Upstash Redis Credentials

1. Go to Upstash Console and create a Redis database if you don't have any
2. Note down your credentials in the details page, we will be using those to connect Redis
   Node in n8n to our Upstash Redis instance.
   <img />

***

### Step 2: Set Up an n8n Project

1. Go to https://n8n.io and create a new project
2. Create a Trigger as Webhook with default settings, this will be our entry point. Our Redis instances gonna watch the visits to this url.
   <img />

***

### Step 3: Create a Redis Node

Now, Let's create a redis node and connect it to our Upstash Redis instance

1. Search for redis in nodes, and select increment action.
   <img />
2. In the opening window, click select credentials, and create new credentials.
   Later, for other redis nodes, this will be saved and used automatically.
   <img />
3. Fill the credentials.

   * Pass your Upstash Token to the password field.
   * Leave the user field blank
   * Pass your Upstash Redis endpoint to the host field. (Leave the https:// part out)
   * If your Upstash Database has a port other than the default 6379, change it here.

   <img />

4. Enable SSL (Upstash Redis requires SSL) and hit the save button.
   <img />

***

### Redis Example: Store the Visit Count per Visitor

1. Track the users with `x-real-ip`
   <img />
2. Add another redis node with get action to see the visit counts
   <img />
3. Read the set visit count with redis get
   <img />

***

### Test Redis Example

Run the workflow and visit the webhook URL, This will send a get request and trigger the workflow run.
Then from the headers your ip will be fetched and in the redis instance you will see `user:user-ip` set to `1`.
As you visit the page it will be incremented and at the end of the workflow you can track and confirm this setup with
the get request.

***

# Prometheus - Upstash Redis Integration
Source: https://upstash.com/docs/redis/integrations/prometheus

To monitor your Upstash database in Prometheus and visualize metrics in Grafana, follow these steps:

<Check >
**Integration Scope**

Upstash Prometheus Integration only covers Pro databases or those included in the Enterprise Plan.

</Check>

## **Step 1: Log in to Your Upstash Account**

1. Open your web browser and navigate to [Upstash](https://console.upstash.com/).
2. Navigate to the main dashboard, where you’ll see a list of your databases.

## **Step 2: Select your Database**

1. Select the database you want to integrate with Prometheus.
2. This will open the database settings, where you can manage various configuration options for your selected database.

![configuration.png]()

3. Enable Prometheus by toggling the switch. This allows you to monitor metrics related to your Upstash database performance, usage, and other key metrics.

## **Step 3: Connect Accounts**

1. After enabling Prometheus, a monitoring token is generated and displayed.

2. Copy this token. This token is unique to your database and is required to authenticate Prometheus with the Upstash metrics endpoint.

<Check >
**Header Format**

You should add monitoring token according to this format `Bearer <MONITORING_TOKEN>`

</Check>

![monitoring-token.png]()

## **Step 4: Set Up Prometheus Connection**
### **Grafana Dashboard Setup**

1. Open your Grafana instance, navigate to the Data Sources section, and select Prometheus as the data source.

![datasource.png]()

2. Enter the data source name, set `https://api.upstash.com/monitoring/prometheus` as the data source address, and then add your monitoring token in the HTTP Headers section.

![headers.png]()

3. Then, click <b>Test and Save</b> to verify that the data source is working properly.

![datasource-final.png]()

### **Prometheus Federation Setup**
Federation lets your Prometheus pull metrics from Upstash’s API and store them in **your own** Prometheus instance, so Grafana can query your Prometheus instead of hitting the Upstash endpoint directly.
#### When to use federation
* You already run Prometheus and want to persist Upstash metrics locally.
* You want to control retention, recording rules, or alerts on Upstash metrics

1. Set up a new scrape job in your Prometheus configuration file (`prometheus.yml`):
```yaml
scrape_configs:
  # Federation job: pull from Upstash API
  - job_name: "federate_upstash"
    honor_labels: true
    metrics_path: "/monitoring/prometheus/federate"
    scheme: https
    params:
      match[]:
        - 'upstash_db_metrics{}'
    static_configs:
      - targets:
          - "api.upstash.com"
    authorization:
      type: Bearer
      credentials: "<MONITORING_TOKEN>"
```
<Check>
This configuration assumes you want to pull all metrics. You can adjust the `match[]` parameter to filter specific metrics if needed.
* `upstash_db_metrics{database_id="your_database_id"}` can be used to pull metrics for a specific database
* `upstash_db_metrics{replica_id=~"us-east-1.*"}` can be used to pull metrics for replicas in a specific region
</Check>

2. Verify the Federation Target
* Reload (or restart) your Prometheus server to apply the new configuration.
* Visit **Prometheus → Status → Targets** and confirm `federate_upstash` is **UP**

## **Step 5: Wait for Metrics Availability**

To visualize your Upstash metrics, you can use a pre-built Grafana dashboard.

Select your Prometheus data source when prompted, and complete the import.

Please check this address to access Upstash Grafana Dashboard <a href="https://grafana.com/grafana/dashboards/22257-upstash-redis-dashboard/"> Dashboard </a>

![grafana-dashboard.png]()

## **Conclusion**

You've now integrated your database with Upstash Prometheus, providing access to improved monitoring and analytics.

Feel free to explore Upstash's features and dashboards to gain deeper insights into your system's performance.

If you encounter any issues or have questions, please refer to the Upstash support documentation or contact our support team for assistance.

# Configure Upstash Ratelimit Strapi Plugin
Source: https://upstash.com/docs/redis/integrations/ratelimit/strapi/configurations

After setting up the plugin, it's possible to customize the ratelimiter algorithm and rates. You can also define different rate limits and rate limit algorithms for different routes.

## General Configurations

<ParamField path="enabled" type="boolean" default="true">
  Enable or disable the plugin.
</ParamField>

## Database Configurations

<ParamField path="token" type="string" required>
  The token to authenticate with the Upstash Redis REST API. You can find this
  credential on Upstash Console with the name `UPSTASH_REDIS_REST_TOKEN`
</ParamField>
<ParamField path="url" type="string" required>
  The URL for the Upstash Redis REST API. You can find this credential on
  Upstash Console with the name `UPSTASH_REDIS_REST_URL`
</ParamField>

<ParamField path="prefix" type="string" default="@strapi">
  The prefix for the rate limit keys. The plugin uses this prefix to store the
  rate limit data in Redis. <br />
  For example, if the prefix is `@strapi`, the key will be
  `@strapi:<method>:<route>:<identifier>`.
</ParamField>

<ParamField path="analytics" type="boolean" default="false">
  Enable analytics for the rate limit. When enabled, the plugin extra insights
  related to your ratelimits. You can use this data to analyze the rate limit
  usage on [Upstash Console](https://console.upstash.com/ratelimit).
</ParamField>

## Strategy

The plugin uses a strategy array to define the rate limits per route. Each strategy object has the following properties:

<ParamField
  path="methods"
  type="('GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH' |'ALL')[]"
  required
>
  An array of HTTP methods to apply the rate limit. <br />
  For example, `["GET", "POST"]`
</ParamField>

<ParamField path="path" type="string" required>
  The path to apply the rate limit. You can use wildcards to match multiple
  routes. For example, `*` matches all routes. <br />
  Some examples: <br />
  * `path: "/api/restaurants/:id"` <br />
  * `path: "/api/restaurants"` <br />
</ParamField>

<ParamField path="identifierSource" type="string" required>
  The source to identifiy the user. Requests with the same identifier will be
  rate limited under the same limit. <br />
  Available sources are: <br />
  * `ip`: The IP address of the user. <br />
  * `header`: The value of a header key. You should pass the source in the `header.<HEADER_KEY>` format. <br />
  For example, `header.Authorization` will use the value of the `Authorization`
</ParamField>

<ParamField path="debug" type="string">
  Enable debug mode for the route. When enabled, the plugin logs the remaining
  limits and the block status for each request. <br />
</ParamField>

<ParamField path="limiter" type="object" required>
  The limiter configuration for the route. The limiter object has the following
  properties:

  <Card>
	  <ParamField path="algorithm" type="'fixed-window' | 'sliding-window' | 'token-bucket'" required>
		The rate limit algorithm to use. For more information related to algorithms, see docs [**here**](/docs/redis/sdks/ratelimit-ts/algorithms). <br />
		* `fixed-window`: The fixed-window algorithm divides time into fixed intervals. Each interval has a set limit of allowed requests. When a new interval starts, the count resets. <br />
		* `sliding-window`:
The sliding-window algorithm uses a rolling time frame. It considers requests from the past X time units, continuously moving forward. This provides a smoother distribution of requests over time. <br />
		* `token-bucket`: The token-bucket algorithm uses a bucket that fills with tokens at a steady rate. Each request consumes a token. If the bucket is empty, requests are denied. This allows for bursts of traffic while maintaining a long-term rate limit.<br />
	  </ParamField>
	  <ParamField path="tokens" type="number" required>
	  	The number of tokens allowed in the time window. <br />
	  </ParamField>
	  <ParamField path="window" type="string" required>
	  	The time window for the rate limit. Available units are `"ms" | "s" | "m" | "h" | "d"` <br />
	  	For example, `20s` means 20 seconds.
	  </ParamField>
	  <ParamField path="refillRate" type="number">
	  	The rate at which the bucket refills. **This property is only used for the token-bucket algorithm.** <br />
	  </ParamField>
  </Card>
</ParamField>

## Examples

<CodeGroup>
```json Apply rate limit for all routes
{
   "strapi-plugin-upstash-ratelimit":{
      "enabled":true,
      "resolve":"./src/plugins/strapi-plugin-upstash-ratelimit",
      "config":{
         "enabled":true,
         "token":"process.env.UPSTASH_REDIS_REST_TOKEN",
         "url":"process.env.UPSTASH_REDIS_REST_URL",
         "strategy":[
            {
               "methods":[
                  "GET",
                  "POST"
               ],
               "path":"*",
               "identifierSource":"header.Authorization",
               "limiter":{
                  "algorithm":"fixed-window",
                  "tokens":10,
                  "window":"20s"
               }
            }
         ],
         "prefix":"@strapi"
      }
   }
}
```

```json Apply rate limit with IP
{
  "strapi-plugin-upstash-ratelimit": {
    "enabled": true,
    "resolve": "./src/plugins/strapi-plugin-upstash-ratelimit",
    "config": {
      "enabled": true,
      "token": "process.env.UPSTASH_REDIS_REST_TOKEN",
      "url": "process.env.UPSTASH_REDIS_REST_URL",
      "strategy": [
        {
          "methods": ["GET", "POST"],
          "path": "*",
          "identifierSource": "ip",
          "limiter": {
            "algorithm": "fixed-window",
            "tokens": 10,
            "window": "20s"
          }
        }
      ],
      "prefix": "@strapi"
    }
  }
}
```

```json Routes with different rate limit algorithms
{
  "strapi-plugin-upstash-ratelimit": {
    "enabled": true,
    "resolve": "./src/plugins/strapi-plugin-upstash-ratelimit",
    "config": {
      "enabled": true,
      "token": "process.env.UPSTASH_REDIS_REST_TOKEN",
      "url": "process.env.UPSTASH_REDIS_REST_URL",
      "strategy": [
        {
          "methods": ["GET", "POST"],
          "path": "/api/restaurants/:id",
          "identifierSource": "header.x-author",
          "limiter": {
            "algorithm": "fixed-window",
            "tokens": 10,
            "window": "20s"
          }
        },
        {
          "methods": ["GET"],
          "path": "/api/restaurants",
          "identifierSource": "header.x-author",
          "limiter": {
            "algorithm": "tokenBucket",
            "tokens": 10,
            "window": "20s",
            "refillRate": 1
          }
        }
      ],
      "prefix": "@strapi"
    }
  }
}
```

</CodeGroup>

# Upstash Ratelimit Strapi Integration
Source: https://upstash.com/docs/redis/integrations/ratelimit/strapi/getting-started

Strapi is an open-source, Node.js based, Headless CMS that saves developers a lot of development time, enabling them to build their application backends quickly by decreasing the lines of code necessary.

You can use Upstash's HTTP and Redis based [Ratelimit package](https://github.com/upstash/ratelimit-js) integration with Strapi to protect your APIs from abuse.

## Getting started

### Installation

<CodeGroup>

```bash npm
npm install --save @upstash/strapi-plugin-upstash-ratelimit
```

```bash yarn
yarn add @upstash/strapi-plugin-upstash-ratelimit
```

</CodeGroup>

### Create database

Create a new redis database on [Upstash Console](https://console.upstash.com/). See [related docs](/docs/redis/overall/getstarted) for further info related to creating a database.

### Set up environment variables

Get the environment variables from [Upstash Console](https://console.upstash.com/), and set it to `.env` file as below:

```shell .env
UPSTASH_REDIS_REST_TOKEN="<YOUR_TOKEN>"
UPSTASH_REDIS_REST_URL="<YOUR_URL>"
```

### Configure the plugin

You can use

<CodeGroup>
```typescript /config/plugins.ts
export default () => ({
  "strapi-plugin-upstash-ratelimit": {
    enabled: true,
    resolve: "./src/plugins/strapi-plugin-upstash-ratelimit",
    config: {
      enabled: true,
      token: process.env.UPSTASH_REDIS_REST_TOKEN,
      url: process.env.UPSTASH_REDIS_REST_URL,
      strategy: [
        {
          methods: ["GET", "POST"],
          path: "*",
          limiter: {
            algorithm: "fixed-window",
            tokens: 10,
            window: "20s",
          },
        },
      ],
      prefix: "@strapi",
    },
  },
});
```

```javascript /config/plugins.js
module.exports = () => ({
  "strapi-plugin-upstash-ratelimit": {
    enabled: true,
    resolve: "./src/plugins/strapi-plugin-upstash-ratelimit",
    config: {
      enabled: true,
      token: process.env.UPSTASH_REDIS_REST_TOKEN,
      url: process.env.UPSTASH_REDIS_REST_URL,
      strategy: [
        {
          methods: ["GET", "POST"],
          path: "*",
          limiter: {
            algorithm: "fixed-window",
            tokens: 10,
            window: "20s",
          },
        },
      ],
      prefix: "@strapi",
    },
  },
});
```

</CodeGroup>

# Replit Templates
Source: https://upstash.com/docs/redis/integrations/replit-templates

## Overview

Explore our collection of example templates showcasing Upstash's capabilities with different frameworks and use cases. Each template comes with a live demo and source code on Replit.

<CardGroup cols={2}>
  <Card
    title="Redis Web Caching"
    icon="database"
    href="https://upstash-web-caching.replit.app"
  >
    Cache SQL queries using Upstash Redis to speed up read requests
  </Card>
  <Card
    title="Rate Limiting Dashboard"
    icon="gauge-high"
    href="https://rate-limit-dashboard.replit.app"
  >
    Implement robust rate limiting using Upstash Redis in a web application
  </Card>
  <Card
    title="Real-time Chat"
    icon="messages"
    href="https://real-time-chat.replit.app"
  >
    Build a real-time chat application using Upstash Redis Pub/Sub with Python
  </Card>
  <Card
    title="RAG Chat Application"
    icon="robot"
    href="https://upstash-rag-chat.replit.app"
  >
    Create an AI chat app with context retrieval using Upstash Vector and Redis
  </Card>
  <Card
    title="Vector Search"
    icon="magnifying-glass"
    href="https://upstash-vector-search.replit.app"
  >
    Implement powerful web search using Upstash Vector Hybrid Search
  </Card>
</CardGroup>

# Sidekiq with Upstash Redis
Source: https://upstash.com/docs/redis/integrations/sidekiq

You can use Sidekiq with Upstash Redis. Sidekiq is a Ruby based queue library with a Redis-based queue storage so you can use with Upstash Redis.

## Example Application

```bash
bundle init
bundle add sidekiq
```

```python
require "sidekiq"
require "sidekiq/api"

connection_url = ENV['UPSTASH_REDIS_LINK']

Sidekiq.configure_client do |config|
    config.redis = {url: connection_url}
end

Sidekiq.configure_server do |config|
    config.redis = {url: connection_url}
end

class EmailService
    include Sidekiq::Worker
    def perform(id, type)
        # Logic goes here. Let's assume sending email by printing to console.
        puts "Emailed to: " +  id + ": " + "'Congrats on " + type + " plan.'"
    end
end

def updateEmail(id, newType)
    jobFound = false

    a = Sidekiq::ScheduledSet.new
    a.each do |job|
        if job.args[0] == id
            job.delete
            jobFound = true
        end
    end

    if jobFound
        EmailService.perform_async(id, ("starting using our service and upgrading it to " + newType))
    else
        EmailService.perform_async(id, ("upgrading to " + newType))
    end
end

def sendEmail(id, type)
    case type
    when "free"
        # if free, delay for 10 seconds.
        EmailService.perform_in("10", id, "free")
    when "paid"
        # if paid, delay for 5 seconds.
        EmailService.perform_in("5", id, "paid")
    when "enterprise"
        # if enterprise, immediately queue.
        EmailService.perform_async(id, "enterprise")
    when "enterprise10k"
        EmailService.perform_async(id, "enterprise10k")
    else
        puts "Only plans are: `free`, `paid` and `enterprise`"
    end
end

def clearSchedules()
    Sidekiq::ScheduledSet.new.clear
    Sidekiq::Queue.new.clear
end
```

## Billing Optimization
Sidekiq accesses Redis regularly, even when there is no queue activity. This can incur extra costs because Upstash charges per request on the Pay-As-You-Go plan. With the introduction of [our Fixed plans](/docs/redis/overall/pricing#all-plans-and-limits), **we recommend switching to a Fixed plan to avoid increased command count and high costs in Sidekiq use cases.**

# Pricing & Billing
Source: https://upstash.com/docs/redis/overall/billing

<Info>
  The most up-to-date pricing is always available at
  [upstash.com/pricing/redis](https://upstash.com/pricing/redis).
</Info>

Upstash Redis has a free tier and two paid plans: **Pay-As-You-Go** (billed per
request) and **Fixed** (a flat monthly price). The numbers below reflect current
pricing — see the [pricing page](https://upstash.com/pricing/redis) for the most
up-to-date information.

## Free tier

Every account starts with a free database. No credit card required.

| Limit              | Free            |
| ------------------ | --------------- |
| Price              | **$0 / month**  |
| Data size          | 256 MB          |
| Monthly commands   | 500K            |
| Monthly bandwidth  | 10 GB           |
| Max commands / sec | 10,000          |
| Max request size   | 10 MB           |
| Max record size    | 100 MB          |
| Databases          | 1               |

## Pay-As-You-Go (PAYG)

Billed per request, so you only pay for what you use. Best for variable or
low-volume workloads where command volume is unpredictable — caching in
serverless functions, edge workloads, and apps that scale to zero.

| Item               | Price                                          |
| ------------------ | ---------------------------------------------- |
| Commands           | **$0.20 per 100K commands**                    |
| Storage            | $0.25 / GB per month (first 1 GB free)         |
| Bandwidth          | Free up to 200 GB / month, then $0.03 / GB     |
| Max data size      | 100 GB                                         |
| Max commands / sec | 10,000                                         |
| Databases          | First 10 free, then $0.50 each (up to 100)      |

To avoid surprise charges, set a **monthly budget** on a PAYG database. When the
budget is reached the database is rate-limited rather than continuing to bill.

## Fixed plans

A flat monthly price with a cap on throughput and data size. Usually cheaper than
PAYG for sustained, high-throughput workloads where command count is consistently
high and predictable. Worker-heavy setups such as Sidekiq, BullMQ, Celery, and
cron jobs are a common fit because they hit Redis continuously even when idle.

You can start on PAYG and switch to a Fixed plan later, or vice versa.

## All plans and limits

| Plan   | Price / month | Data size | Bandwidth | Commands / sec | Max request | Max record |
| ------ | ------------- | --------- | --------- | -------------- | ----------- | ---------- |
| Free   | $0            | 256 MB    | 10 GB     | 10,000         | 10 MB       | 100 MB     |
| PAYG   | Per request   | 100 GB    | 200 GB*   | 10,000         | 10 MB       | 100 MB     |
| 250 MB | $10           | 250 MB    | 50 GB     | 10,000         | 10 MB       | 100 MB     |
| 1 GB   | $20           | 1 GB      | 100 GB    | 10,000         | 10 MB       | 200 MB     |
| 5 GB   | $100          | 5 GB      | 500 GB    | 10,000         | 20 MB       | 300 MB     |
| 10 GB  | $200          | 10 GB     | 1 TB      | 10,000         | 30 MB       | 400 MB     |
| 50 GB  | $400          | 50 GB     | 5 TB      | 10,000         | 50 MB       | 500 MB     |
| 100 GB | $800          | 100 GB    | 10 TB     | 16,000         | 75 MB       | 1 GB       |
| 500 GB | $1,500        | 500 GB    | 20 TB     | 16,000         | 100 MB      | 5 GB       |

<sub>*PAYG bandwidth is free up to 200 GB/month, then $0.03/GB. Enterprise plans
support larger data sizes and custom limits — [contact us](/docs/common/help/support).</sub>

## How billing works

* **What counts as a command.** Pricing is based on the number of commands (requests)
  executed. This applies equally to the [REST API](/docs/redis/features/restapi) and to
  commands made over the Redis protocol — one command is one billable request in both
  cases.
* **Global databases.** With [Global replication](/docs/redis/features/globaldatabase),
  each write is replicated to every read region, and each replicated write counts as a
  command for billing. Adding read regions to a Fixed plan costs 50% of the base tier
  price per region.
* **Storage.** On PAYG, storage is billed at $0.25/GB per month with the first 1 GB free.
  Fixed plans include storage up to the plan's data-size cap.
* **Prod Pack.** Production features (high availability, encryption, SOC-2, advanced
  monitoring) are available as an add-on for $200/month per database. See
  [Prod Pack & Enterprise](/docs/redis/overall/enterprise).

## Optimizing cost

* On PAYG, a high baseline of background commands (for example, [Sidekiq](/docs/redis/integrations/sidekiq)
  polling) can drive up command count even when there is no real workload — switching to
  a Fixed plan caps that cost.
* Prefer pipelines and `MULTI`/`EXEC` to reduce round trips; note that each command inside
  a pipeline or transaction is still billed individually.
* Set a monthly budget on PAYG databases to bound spend.

# Changelog
Source: https://upstash.com/docs/redis/overall/changelog

<Update label="March 2026">
Added [Upstash Redis Search](/docs/redis/search/introduction) feature, a new extension for searching Redis data. It works with JSON, Hash, and String data, automatically keeps indexes in sync with Redis writes, and supports full-text search, filtering, aggregations, aliases, highlighting, fuzzy matching, phrase queries, and regex matching.

Redis Search is available through the [@upstash/redis](/docs/redis/sdks/ts/overview) and [upstash-redis](/docs/redis/sdks/py/overview) SDKs. If you already use `node-redis` or `ioredis`, you can use the [`@upstash/search-redis`](/docs/redis/search/adapters/node-redis) and [`@upstash/search-ioredis`](/docs/redis/search/adapters/ioredis) wrappers without switching clients.

New Redis Search commands:
* [`SEARCH.CREATE`](/docs/redis/commands/search/search-create): Create a search index
* [`SEARCH.DROP`](/docs/redis/commands/search/search-drop): Remove a search index
* [`SEARCH.DESCRIBE`](/docs/redis/commands/search/search-describe): Return metadata about a search index
* [`SEARCH.WAITINDEXING`](/docs/redis/commands/search/search-waitindexing): Wait until pending index updates are visible to queries
* [`SEARCH.QUERY`](/docs/redis/commands/search/search-query): Search documents with a JSON filter
* [`SEARCH.COUNT`](/docs/redis/commands/search/search-count): Count matching documents without returning them
* [`SEARCH.AGGREGATE`](/docs/redis/commands/search/search-aggregate): Compute analytics over matching documents
* [`SEARCH.ALIASADD`](/docs/redis/commands/search/search-aliasadd), [`SEARCH.ALIASDEL`](/docs/redis/commands/search/search-aliasdel), and [`SEARCH.LISTALIASES`](/docs/redis/commands/search/search-listaliases): Manage search index aliases
</Update>

<Update label="December 2025">
Added [Redis Functions](https://redis.io/docs/latest/develop/programmability/functions-intro/) support. New commands are:
* [`FCALL`](/docs/redis/commands/functions/fcall): Call a function with read/write capabilities
* [`FCALL_RO`](/docs/redis/commands/functions/fcall-ro): Call a function in read-only mode
* `FUNCTION DELETE`, `FUNCTION FLUSH`, `FUNCTION KILL`, `FUNCTION LIST`, `FUNCTION LOAD` and `FUNCTION STATS`

New Hash commands:
* [`HGETDEL`](/docs/redis/commands/hash/hgetdel): Get and delete hash fields atomically
* [`HGETEX`](/docs/redis/commands/hash/hgetex): Get hash fields with expiration support
* [`HSETEX`](/docs/redis/commands/hash/hsetex): Set hash fields with expiration support

New Stream Commands:
* [`XDELEX`](/docs/redis/commands/streams/xdelex): Extended delete for streams
* [`XACKDEL`](/docs/redis/commands/streams/xackdel): Acknowledge and delete stream entries

New Bit operations added:
* `BITOP DIFF`: A bit is set only if it's set in all source bitmaps
* `BITOP DIFF1`: A bit is set if it's set in the first key but not in any of the other keys
* `BITOP ANDOR`: A bit is set if it's set in X and also in one or more of Y1, Y2, ...
* `BITOP ONE`: A bit is set if it's set in exactly one source key
</Update>

<Update label="April 2025">
Added HASH expiration support. New commands are:
* [`HEXPIRE`](/docs/redis/commands/hash/hexpire): Set expiration time in seconds
* [`HPEXPIRE`](/docs/redis/commands/hash/hpexpire): Set expiration time in milliseconds
* [`HEXPIREAT`](/docs/redis/commands/hash/hexpireat): Set expiration time as Unix timestamp in seconds
* [`HPEXPIREAT`](/docs/redis/commands/hash/hpexpireat): Set expiration time as Unix timestamp in milliseconds
* [`HTTL`](/docs/redis/commands/hash/httl): Get remaining time to live in seconds
* [`HPTTL`](/docs/redis/commands/hash/hpttl): Get remaining time to live in milliseconds
* [`HEXPIRETIME`](/docs/redis/commands/hash/hexpiretime): Get absolute expiration time as Unix timestamp in seconds
* [`HPEXPIRETIME`](/docs/redis/commands/hash/hpexpiretime): Get absolute expiration time as Unix timestamp in milliseconds
* [`HPERSIST`](/docs/redis/commands/hash/hpersist): Remove expiration from hash fields
</Update>

<Update label="Feb 2025">
Added [`EVAL_RO`](/docs/redis/commands/scripting/eval-ro) and [`EVALSHA_RO`](/docs/redis/commands/scripting/evalsha-ro)
commands introduced in Redis 7.
</Update>

<Update label="July 2024">
* Added REST API support for [`MONITOR`](/docs/redis/commands/server/monitor) and [`SUBSCRIBE`](/docs/redis/commands/pub-sub/subscribe)
commands using [SSE](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events).
See [Monitor](../features/restapi#monitor-command) and [Subscribe](../features/restapi#subscribe-command) docs.
* Added [`JSON.MSET`](/docs/redis/commands/json/json-mset) and [`JSON.MERGE`](/docs/redis/commands/json/json-merge) commands.
* Introduced the `IP Allowlist` feature for enhanced security on newly created databases. By default, all IP addresses will be allowed.
However, access can be restricted by specifying permitted IP addresses or CIDR ranges.
</Update>

<Update label="June 2024">
* Added AWS AP-NorthEast-1 Japan region.
* Added an option to return REST response in [`RESP2`](https://redis.io/docs/latest/develop/reference/protocol-spec/) format instead of `JSON`.
See [REST API docs](/docs/redis/features/restapi#resp2-format-responses) for more information.
</Update>

<Update label="April 2024">
* Implemented [`MONITOR`](/docs/redis/commands/server/monitor) command
* Implemented Redis [keyspace notifications](/docs/redis/howto/keyspacenotifications)
* Implemented [`WAIT`](/docs/redis/commands/generic/wait) and [`WAITAOF`](/docs/redis/commands/generic/waitaof) commands
* Added `lag` field to [`XINFO GROUPS`](/docs/redis/commands/streams/xinfo-groups)
* Added [`CLIENT ID`](/docs/redis/commands/connection/client-id) subcommand
* Added password strength check to [`ACL SETUSER`](/docs/redis/commands/server/acl-setuser) command
</Update>

<Update label="February 2024">
* Fixed JSON commands with empty keys
* Fixed a panic on `XTRIM` and `XDEL`
* Added `CLIENT SETNAME/NAME/LIST` subcommands
* Implemented near exact trim for streams
</Update>

<Update label="September 2023">
* Implemented some missing Redis commands:
    * `DUMP`
    * `RESTORE`
    * `ZMPOP`
    * `BZMPOP`
    * `LMPOP`
    * `BLMPOP`
    * `SINTERCARD`
* Added support for `BIT/BYTE` flag to `BITPOS` and `BITCOUNT` commands
* Added support for `XX`, `NX`, `GT`, and `LT` arguments to `EXPIRE` commands
* Allowed `NX` and `GET` args to be used together in `SET` command
</Update>

# Compare
Source: https://upstash.com/docs/redis/overall/compare

In this section, we will compare Upstash with alternative cloud based solutions.

## AWS ElastiCache

* **Serverless Pricing:** Elasticache does not have a serverless pricing model.
  The price does not scale to zero. You need to pay for the instances even when
  you do not use them. Upstash charges per request.
* **REST API:** Unlike ElastiCache, Upstash has a built-in REST API, so you can
  access from environments where TCP connections are not allowed such as edge
  functions at Cloudflare Workers.
* **Access:** Elasticache is designed to be used inside AWS VPC. You can access
  Upstash from anywhere.
* **Durability:** Upstash persists your data to the block storage in addition to
  memory so you can use it as your primary database.

## AWS MemoryDB

* **Serverless Pricing:** Similar to Elasticache, MemoryDB does not offer a
  serverless pricing model. The pricing does not scale down to zero, and even
  the most affordable instance costs over \$200 per month. This means you are
  required to pay for the instances regardless of usage. In contrast, Upstash
  follows a different approach by charging per request. With Upstash, you only
  incur charges when actively using your Redis database, ensuring that you do
  not have to pay when it's not in use.

* **REST API:** Unlike MemoryDB, Upstash has a built-in REST API, so you can
  access from environments where TCP connections are not allowed such as edge
  functions at Cloudflare Workers.

* **Access:** MemoryDB is designed to be used inside AWS VPC. You can access
  Upstash from anywhere.

## Redis Labs

* **Serverless Pricing:** Redis Labs does not have a serverless pricing model
  either. The price does not scale to zero. You need to pay for the instances
  even when you do not use them. Upstash charges per request, so you only pay
  for your real usage.
* **REST API:** Unlike Redis Labs, Upstash has a built-in REST API, so you can
  access from environments where TCP connections are not allowed such as edge
  functions at Cloudflare Workers.
* **Durability:** Upstash persists your data to the block storage instantly in
  addition to the memory, so you can use it as your primary database.
* **Full-text search:** Upstash offers [Upstash Redis Search](/docs/redis/search/introduction), a fast
  Tantivy-based full-text search extension that works with JSON, Hashes, and Strings out of the box
  and stays automatically in sync as you write.
* **JSON:** Upstash supports the `JSON.*` commands natively. See [compatibility](/docs/redis/overall/compatibility).

## AWS DynamoDB

* **Latency:** DynamoDB is a disk based data storage. Both write and read
  latency are much higher than Redis. Check our
  [benchmark app](https://serverless-battleground.vercel.app/) to get an idea.

* **Complex Pricing:** Initially, DynamoDB may appear cost-effective, but if you
  begin utilizing advanced features such as DAX or Global Tables, you might
  encounter unexpected expenses on your AWS bill. In contrast, Upstash offers a
  more transparent pricing policy, ensuring that you are not taken by surprise.
  With Upstash, you can optionally set a budget to cap your maximum costs,
  providing clarity and preventing any unwelcome surprises in your billing.

* **Portability:** DynamoDB is exclusive to AWS and cannot be used outside of
  the AWS platform. However, Redis is supported by numerous cloud providers and
  can also be self-hosted. Upstash provides compatibility with Redis, ensuring
  vendor neutrality.

* **Testability:** Running a local Redis for testing purposes is much easier
  than running a local DynamoDB. Check
  [this](https://stackoverflow.com/questions/26901613/easier-dynamodb-local-testing).

## FaunaDB

* **Latency:** FaunaDB is a globally consistent database. Consistency at global
  level comes with performance cost. Check our
  [benchmark app](https://serverless-battleground.vercel.app/) to get an idea.

* **Complex Pricing:** FaunaDB has a complicated pricing. It has 6 different
  dimensions to calculate the price. Check
  [this article](https://docs.fauna.com/fauna/current/manage/plans-billing/billing/)
  where the pricing is explained. If your use case is write heavy and
  if your requests have bigger payloads, then it can become expensive very easily.
  On the other hand, Upstash has different options for different needs and
  pricing is simple for all options. You pay per request in addition to
  storage cost which is generally much smaller amount.

* **Portability:** FaunaDB is only supported by Fauna Inc. On the other hand,
  you can use Redis almost in all cloud providers as well as you can host Redis
  yourself. Upstash does not lock you to any vendor.

* **Testability:** Running a local Redis for testing purposes is much easier
  than running a local FaunaDB. Check
  [this](https://dev.to/englishcraig/how-to-set-up-faunadb-for-local-development-5ha7).

## What makes Upstash different?

You have a new project and you do not know how many requests will it receive?
You love the performance and simplicity of Redis. But all Redis Cloud services
charge you per instance or per GB of memory. But maybe your application will not
receive big traffic at first, then why will you pay the full amount?
Unfortunately none of the current Redis cloud products provides a real
`pay-per-use` pricing model.

Let's do a simple calculation. Say I have a 1GB Redis database and I receive 1
million requests per month. For ElastiCache (cache.t3.small, \$0.034 hourly) this
costs at least \$24 not including data transfer and storage cost. For RedisLabs,
the 1GB plan costs \$22 per month. For Upstash the price is \$0.2 per 100k
requests. For 1 million, it is \$2 plus the storage cost that is \$0.25. So for
1GB, 1M request per months, ElastiCache is \$24, RedisLabs is \$22, Upstash is
$2.25.

  <img />

**What if your product becomes popular and starts to gain high and steady
traffic?**

Most of the serverless products start to lose their spell if the service
receives steady and high traffic as it starts to cost higher than
server/instance based pricing models. To overcome this situation we give you
the option to switch to a [Fixed plan](/docs/redis/overall/pricing). On a Fixed plan you set a
fixed price per month with a restriction on max throughput and data size. For high and
steady throughput use cases, Fixed plans cost less than pay-as-you-go.
The good thing is you can start your database with pay-as-you-go pricing and
move to a Fixed plan when you want. See [pricing](/docs/redis/overall/pricing) and [enterprise plans](/docs/redis/overall/enterprise) for more information.

Even if you choose not to upgrade to a Fixed plan, Upstash gives you control
over your spending through optional budgets. By default, there is no monthly
price ceiling on a pay-as-you-go database, but you can set a budget to cap
your costs. When a database hits its configured budget, its operations are
stopped, ensuring you never exceed the amount you've set.

# Redis® API Compatibility: supported commands, modules, and protocols
Source: https://upstash.com/docs/redis/overall/compatibility

<Note>
  Upstash supports both native Redis TCP and HTTPS REST.
</Note>

Upstash supports Redis client protocol up to version `8.2`. We are also gradually adding changes introduced in later versions.

<Card title="Browse the Redis command reference" icon="book-open" href="/redis/commands/overview" horizontal>
  See every supported Redis command, organized by category.
</Card>

SDK command references may cover a smaller set of typed helpers.
For example, see the [TypeScript SDK command reference](/docs/redis/sdks/ts/commands/overview) for the commands documented in `@upstash/redis`.

Most unsupported items are on our roadmap. If you need a feature that we do not support, email [support@upstash.com](mailto:support@upstash.com) so we can let you know when we plan to support it.

# Prod Pack & Enterprise
Source: https://upstash.com/docs/redis/overall/enterprise

Upstash has Prod Pack and Enterprise plans for customers with critical production workloads. Prod Pack and Enterprise plans include additional monitoring and security features in addition to higher capacity limits and more powerful resources

Prod Pack -> Per database

Enterprise contract -> Per account

Prod Pack are an add-on per database available to both pay-as-you-go and fixed-price plans, not per account. You can have databases on different plans in the same account and each is charged separately. Meanwhile, Enterprise plans are per account, not per database. All of your databases can be included in the same Enterprise plan covering all of your databases.

All features of Prod Pack and Enterprise plan for Upstash Redis are detailed below.

## How to Upgrade

You can activate Prod Pack on the database details page in the [Upstash Console](https://console.upstash.com). For the Enterprise plan, please create a request through the or contact [support@upstash.com](mailto:support@upstash.com).

# Prod Pack Features
These features are available on databases with Prod Pack.

### Uptime SLA
All Prod Pack databases come with an SLA guaranteeing 99.99% uptime. For mission-critical data where uptime is crucial, we recommend Prod Pack plans. Learn more about [Uptime SLA](/docs/common/help/sla).

### SOC-2 Type 2 Compliance & Report
SOC-2 Type 2 controls apply at the platform level. Prod Pack adds customer-facing compliance support, including SOC-2 report access, contractual commitments, and audit-ready features such as encryption at rest, credential protection, and advanced monitoring. Once enabled, you can request the report through the [Upstash Trust Center](https://trust.upstash.com/) or by contacting [support@upstash.com](mailto:support@upstash.com).

### Multi-Zone High Availability

With Prod Pack add-on, regions of your database are deployed with [multi-zone high availability](/docs/redis/features/replication#high-availability). If one replica or availability zone fails, another replica in a different zone continues serving traffic in the same region. This ensures that your database remains available without any additional latency.

<Accordion title="How multi-zone affects pricing">

**Fixed plans**

* The number of commands is not charged on fixed plans; an unlimited number of commands is allowed, and this does not change when multi-zone high availability is enabled.
* Multi-zone high availability slightly increases bandwidth usage for write commands, since each write is replicated to more replicas.

**Pay-as-you-go**

* The number of commands is billed on pay-as-you-go, but multi-zone high availability does not change the command count per write. Reads are also unaffected, since a read only hits a single replica. Only writes are replicated to additional replicas.
* Multi-zone high availability slightly increases bandwidth usage. On pay-as-you-go, bandwidth is only charged beyond 200 GB, so this increase can affect your bill.

For more details, see the [Redis pricing page](https://upstash.com/pricing/redis).

</Accordion>

### More Backup Capability

Backups up to 3 days are possible with the Prod Pack add-on.

### Encryption at Rest

Encrypts the block storage where your data is persisted and stored.

### Prometheus Metrics

Prometheus is an open-source monitoring system widely used for monitoring and alerting in cloud-native and containerized environments.

Upstash Prod Pack and Enterprise plans offer Prometheus metrics collection, enabling you to monitor your Redis databases with Prometheus in addition to console metrics. Learn more about [Prometheus integration](/docs/redis/integrations/prometheus).

### Datadog Integration

Upstash Prod Pack and Enterprise plans include integration with Datadog, allowing you to monitor your Redis databases with Datadog in addition to console metrics. Learn more about [Datadog integration](/docs/redis/howto/datadog).

### More metrics on the Console

Max interval of the metrics that are available on the Upstash Console increases from one week to one month for databases with Prod Pack.

# Enterprise Features

All Prod Pack features are included in the Enterprise plan. Additionally, Enterprise plans include:

### Custom Limits
Get a custom-tailored plan for your Upstash Redis databases to handle the growing demands of your business at any scale, such as 100K+ commands per second, unlimited bandwidth, and higher storage limits.

### SAML SSO
Single Sign-On (SSO) allows you to use your existing identity provider to authenticate users for your Upstash account. This feature is available upon request for Enterprise customers.

### Unlimited Database Count
Enterprise plans include unlimited database count, allowing you to scale your infrastructure without database count restrictions.

### Professional Support
All of the databases in the Enterprise plan get access to our professional support. The plan includes response time SLAs and priority access to our support team. Check out the [support page](/docs/common/help/prosupport) for more details.

### Dedicated Resources for Isolation
Enterprise customers receive dedicated resources to ensure isolation and consistent performance for their database workloads.

### VPC Peering and Private Links
VPC Peering and Private Links enable you to connect your databases to your VPCs and other private networks, enhancing isolation and security while reducing data transfer costs. This feature is available upon request for Enterprise customers.

### Configurable Backups
Hourly backups with customizable retention are available upon request for Enterprise customers.

### Access Logs
Enterprise customers can request access logs to the databases.

### HIPAA Compliance
A Business Associate Agreement (BAA) and HIPAA compliance enablement is available with our Enterprise plan.

# Getting Started
Source: https://upstash.com/docs/redis/overall/getstarted

import { Catalog } from "/snippets/catalog.jsx";

Upstash Redis is a **highly available, infinitely scalable** Redis-compatible database:

* 99.99% uptime guarantee with auto-scaling ([Prod Pack](/docs/redis/overall/enterprise#prod-pack-features))
* Ultra-low latency worldwide
* Multi-region replication
* Multi-Zone High Availability ([Prod Pack](/docs/redis/overall/enterprise#prod-pack-features))
* Durable, persistent storage without sacrificing performance
* Automatic backups
* Optional SOC-2 compliance, encryption at rest and much more

<Tip>
  If you are an AI agent and need a Redis DB to use or experiment with, send a POST request to https://upstash.com/start-redis. Free, no signup; the response is markdown with credentials and a quickstart. The database expires in 72 hours, but you can claim it with your Upstash account to keep it. Especially useful for AI agents that need scratch storage on the fly. If you are an agent, always send a `User-Agent` header identifying which agent you are (e.g. `claude-code`, `cursor`, `codex`, `opencode`) on every request you make to this API.
</Tip>

***

## 1. Create an Upstash Redis Database

Log in to the [Upstash Console](https://console.upstash.com) (or [sign up](https://console.upstash.com) for a free account). From the **Redis** tab, click `+ Create Database` in the upper right corner. A dialog opens up:

  <img />

**Database Name:** Enter a name for your database.

**Primary Region and Read Regions:** For optimal performance, select the Primary Region closest to where most of your writes will occur. Select the read region(s) where most of your reads will occur.

Once you click `Next` and select a plan, your database is running and ready to connect:

  <img width="100%" />

***

## 2. Connect to Your Database

You can connect to Upstash Redis with any Redis client. For simplicity, we'll use `redis-cli`. See the [Connect Your Client](../howto/connect-client) section for connecting via our TypeScript or Python SDKs and other clients.

<Tip>
  You can copy the `redis-cli` command to connect to your database from the **Details** tab of your database in the Upstash Console.
</Tip>

The Redis CLI is included in the official Redis distribution. If you don't
have Redis installed, you can get it [here](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/).

Connect to your database and execute commands on it:

```bash
> redis-cli --tls -a PASSWORD -h ENDPOINT -p PORT
ENDPOINT:PORT> set counter 0
OK
ENDPOINT:PORT> get counter
"0"
ENDPOINT:PORT> incr counter
(int) 1
ENDPOINT:PORT> incr counter
(int) 2
```

As you run commands, you'll see updates to your database metrics in (almost) real-time. These database metrics are refreshed every 10 seconds.

  <img width="100%" />

Congratulations! You have created an ultra-fast Upstash Redis database! 🎉

<Check>
**New: Manage Upstash Redis with your agent**

Manage Upstash Redis databases from Claude and other AI tools by using our [MCP server](/docs/agent-resources/mcp). To let an agent run commands directly against a single database, use the dedicated [Redis MCP](/docs/redis/sdks/mcp).
</Check>

## Next steps

* [Connect your client](../howto/connect-client): TypeScript, Python, Go, Java, and other Redis clients.
* [Use cases](/docs/redis/overall/usecases): caching, rate limiting, queues, pub/sub, AI workloads, and more.
* [Upstash Redis Search](/docs/redis/search/introduction): full-text search built into Upstash Redis.
* [REST API](/docs/redis/features/restapi): connect from edge and serverless runtimes where TCP is restricted.
* [Pricing & Billing](/docs/redis/overall/billing): free tier, Pay-As-You-Go, and Fixed plan limits, plus how billing works.

## SDKs

<Catalog product="redis" type="sdk" cols={3} />

# llms.txt
Source: https://upstash.com/docs/redis/overall/llms-txt

# Pricing & Limits
Source: https://upstash.com/docs/redis/overall/pricing

# Pricing & Limits
Source: https://upstash.com/docs/redis/overall/pricingold

# Python SDK
Source: https://upstash.com/docs/redis/overall/pythonredis

# Rate Limit SDK
Source: https://upstash.com/docs/redis/overall/ratelimit

# Typescript SDK
Source: https://upstash.com/docs/redis/overall/redis

# Use Cases
Source: https://upstash.com/docs/redis/overall/usecases

The data store behind Upstash is [compatible](/docs/redis/overall/compatibility)
with almost all Redis® API. So you can use Upstash for the Redis®' popular use
cases such as:

* General caching
* Session caching
* Rate limiting (see [`@upstash/ratelimit`](https://github.com/upstash/ratelimit))
* Queues and background jobs (works with [Sidekiq](/docs/redis/integrations/sidekiq), [BullMQ](/docs/redis/integrations/bullmq), [Celery](/docs/redis/integrations/celery))
* Distributed locking
* Pub/Sub
* Feature flags
* Full-text search inside Redis with [Upstash Redis Search](/docs/redis/search/introduction) (auto-synced, works with JSON, Hashes, and Strings out of the box)
* Leaderboards
* Recommendations
* Usage metering (counting)
* Content filtering
* LLM response caching, chat history, and agent memory for AI applications

Upstash runs anywhere your application runs: traditional servers and containers (EC2, Render, Fly.io, Railway, Kubernetes) over native Redis TCP, serverless functions (AWS Lambda, Vercel Functions, Google Cloud Functions) over TCP or REST, and edge runtimes (Cloudflare Workers, Vercel Edge, Deno Deploy) over the REST API.

Check Salvatore's [blog](http://antirez.com/post/take-advantage-of-redis-adding-it-to-your-stack.html) post. You can find lots of similar articles about the common use cases of Redis.

## Key Value Store and Caching for Next.js Application

Next.js is increasingly becoming the preferred method for developing dynamic and fast web applications in an agile manner. It owes its popularity to its server-side rendering capabilities and API routes supported by serverless functions, including Vercel serverless and edge functions. Upstash Redis is a great fit with Next.js applications due to its serverless model and its REST-based APIs. The REST API plays a critical role in enabling access from edge functions while also addressing connection issues in serverless functions.

Check the blog post:
[Speed up your Next.js application with Redis](https://upstash.com/blog/nextjs-caching-with-redis)

## Redis for Vercel Functions

Vercel stands out as one of the most popular cloud platform for web developers, offering continuous integration, deployment, CDN and serverless functions. However, when it comes to databases, you'll need to rely on external data services to support dynamic applications.

That's where Upstash comes into play as one of the most favored data solutions within the Vercel platform. Here are some reasons that contribute to Upstash's popularity in the Vercel ecosystem:

* No connection problems thanks to
  [Upstash SDK](https://github.com/upstash/upstash-redis) built on Upstash REST
  API.
* Edge runtime does not allow TCP based connections. You can not use regular
  Redis clients. [Upstash SDK](https://github.com/upstash/upstash-redis) works
  on edge runtimes without a problem.
* Upstash has a [Vercel add on](https://vercel.com/integrations/upstash) where
  you can easily integrate Upstash to your Vercel projects.

## Storage For Lambda Functions (FaaS)

People use Lambda functions for various reasons, with one of the primary advantages being their cost-effectiveness – you only pay for what you actually use, which is great. However, when it comes to needing a storage layer, AWS recommends DynamoDB. DynamoDB does offer a serverless mode, which sounds promising until you encounter its latency when connecting and operating within Lambda Functions. Unfortunately, DynamoDB's latency may not be ideal for Lambda Functions, where every second of latency can have a significant impact on costs. At this point, AWS suggests using ElastiCache for low-latency data storage, which is also a Redis® cache as a service – a positive aspect. However, it's worth noting that ElastiCache is not serverless, and you have to pay based on what you provision, rather than what you use. To be honest, the pricing may not be the most budget-friendly option. This leaves you with two alternatives:

* DynamoDB: Serverless but high latency
* ElastiCache: Low latency but not serverless.

Until you meet the Upstash. Our sole mission is to provide a Redis® API
compatible database that you love in the serverless model. In Upstash, you pay
per the number of requests you have sent to your database. So if you are not
using the database you pay almost nothing. (Almost, because we charge for the
storage. It is a very low amount but still it is there.)

We believe that Upstash is the best storage for your Lambda Functions because:

* Serverless just like Lambda functions itself
* Designed for low latency data access
* The lovely simple Redis® API

## Detailed Tutorials

<CardGroup cols={2}>
  <Card
    title="Building Analytics with Redis"
    icon="chart-line"
    href="https://upstash.com/blog/building-analytics-with-redis"
  >
    Count and query events with counters, bitmaps, and Redis Search
  </Card>
  <Card
    title="Agent Memory with Redis Search"
    icon="brain"
    href="/redis/tutorials/agent_memory"
  >
    Short-term and long-term memory for AI agents on Upstash Redis
  </Card>
  <Card
    title="Caching in Laravel with Redis"
    icon="laravel"
    href="/redis/tutorials/laravel_caching"
  >
    Speed up your Laravel application with Upstash Redis caching
  </Card>
  <Card
    title="Cloudflare Workers with Websockets and Redis"
    icon="cloudflare"
    href="/redis/tutorials/cloudflare_websockets_redis"
  >
    Build realtime apps on Cloudflare Workers backed by Upstash Redis
  </Card>
</CardGroup>

- [AWS Lambda](https://upstash.com/docs/redis/quickstarts/aws-lambda.md)
- [Azure Functions](https://upstash.com/docs/redis/quickstarts/azure-functions.md)
- [Cloudflare Workers](https://upstash.com/docs/redis/quickstarts/cloudflareworkers.md)
- [Deno Deploy](https://upstash.com/docs/redis/quickstarts/deno-deploy.md)
- [DigitalOcean](https://upstash.com/docs/redis/quickstarts/digitalocean.md)
- [Django](https://upstash.com/docs/redis/quickstarts/django.md)
- [Elixir](https://upstash.com/docs/redis/quickstarts/elixir.md): Tutorial on Using Upstash Redis In Your Phoenix App and Deploying it on Fly.
- [FastAPI](https://upstash.com/docs/redis/quickstarts/fastapi.md)
- [Fastly](https://upstash.com/docs/redis/quickstarts/fastlycompute.md)
- [Flask](https://upstash.com/docs/redis/quickstarts/flask.md)
- [Fly.io](https://upstash.com/docs/redis/quickstarts/fly.md)
- [Google Cloud Run functions](https://upstash.com/docs/redis/quickstarts/google-cloud-functions.md)
- [ioredis note](https://upstash.com/docs/redis/quickstarts/ioredisnote.md)
- [Koyeb](https://upstash.com/docs/redis/quickstarts/koyeb.md)
- [Laravel](https://upstash.com/docs/redis/quickstarts/laravel.md)
- [App Router](https://upstash.com/docs/redis/quickstarts/nextjs-app-router.md)
- [Pages Router](https://upstash.com/docs/redis/quickstarts/nextjs-pages-router.md)
- [AWS Lambda](https://upstash.com/docs/redis/quickstarts/python-aws-lambda.md)
- [SST](https://upstash.com/docs/redis/quickstarts/sst.md)
- [Supabase Functions](https://upstash.com/docs/redis/quickstarts/supabase.md)
- [App Router](https://upstash.com/docs/redis/quickstarts/vercel-functions-app-router.md)
- [Pages Router](https://upstash.com/docs/redis/quickstarts/vercel-functions-pages-router.md)
- [Vercel Python Runtime](https://upstash.com/docs/redis/quickstarts/vercel-python-runtime.md)

# Agent Analytics
Source: https://upstash.com/docs/redis/sdks/agent-analytics

# Vercel AI SDK Memory, RAG & Chat History with Redis
Source: https://upstash.com/docs/redis/sdks/agentkit/ai-sdk

[Upstash AgentKit](https://github.com/upstash/agentkit) builds AI agents on Upstash Redis: memory,
conversation history, caching, and RAG, with no separate vector database. The semantic features run on
[Upstash Redis Search](/docs/redis/search/introduction) and its `$smart` fuzzy operator.

`@upstash/agentkit-ai-sdk` is the [Vercel AI SDK](https://ai-sdk.dev) adapter — drop-ins for
`generateText` / `streamText`. `redis` defaults to `Redis.fromEnv()`, so you import only from this
package.

| Import | Feature |
| --- | --- |
| `createChatHistory` | Durable chat history on Redis Search — save, list, and `$smart`-search a user's transcripts. |
| `createMemoryTools` | `recall_memory` + `save_memory` tools so the model reads and writes long-term memory. |
| `createSearchTools` | `search` / `aggregate` / `count` tools over a Redis Search index (this is how you do RAG). |
| `createRateLimit` | A configured Upstash Ratelimit to call before the model. |
| `cachedTools` | Memoize a map of AI SDK tools' results in Redis. |

```bash
npm install @upstash/agentkit-ai-sdk @upstash/redis ai
```

<Note>
  AgentKit reads `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN` from the environment by default.
  Pass your own `@upstash/redis` client as `redis` to any helper to override.
</Note>

## How to store chat history with the AI SDK

A Redis-backed `ChatHistory<UIMessage>`, the durable source of truth for your conversations. `userId`
comes from your auth session; `chatId` is the `useChat` id that the client posts. Save the full
transcript from your route's `onFinish`:

```ts
// app/api/chat/route.ts
import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream } from "ai";
import { createChatHistory } from "@upstash/agentkit-ai-sdk";

const history = createChatHistory();

export async function POST(req: Request) {
  const userId = await getSessionUserId(req); // your auth session, never a client-sent id
  const { id: chatId, messages } = await req.json(); // useChat posts its chat id + the full transcript

  const result = streamText({ model, messages: convertToModelMessages(messages) });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      originalMessages: messages,
      onFinish: ({ messages }) =>
        history.saveChat({ userId, sessionId: chatId, messages, title: "New chat" }),
    }),
  });
}
```

To load a chat, take `chatId` from the page route and `userId` from the session, then seed `useChat`:

```ts
const chat = await history.getChat({ userId, sessionId: chatId }); // full transcript, or null
const chats = await history.listChats({ userId, limit: 50 }); // summaries, no messages
const hits = await history.searchChats({ userId, query: "headphones", target: "both", limit: 20 });
// client: useChat({ id: chatId, messages: chat?.messages ?? [] })
```

<AccordionGroup>
  <Accordion title="Config and how it's stored">
    ```ts
    createChatHistory({
      redis, // optional: defaults to Redis.fromEnv()
      prefix: "agentkit:chat", // optional: base key prefix
      indexName: "agentkit_chat", // optional: index name (defaults to the prefix)
      ttlSeconds: 60 * 60 * 24 * 30, // optional: per-chat TTL (default: no expiry)
    });
    ```

    Each chat is one JSON doc at `agentkit:chat:<userId>:<sessionId>` (keyed per user, so two users
    can't collide on a `sessionId`), indexed over `userId` + `sessionId` (filters) and `userMessages` +
    `modelMessages` (`$smart` fuzzy text); the raw `messages` array rides along unindexed. `saveChat`
    overwrites the **whole** array (no delta merge) — `useChat` sends the full conversation. Other
    methods: `getChat` / `deleteChat` (`{ userId, sessionId }`), `listChats` / `searchChats`
    (`{ userId }`).
  </Accordion>
  <Accordion title="Security — userId is the tenant boundary">
    Every method takes a single object; `userId` is **required, non-empty, and may not contain `:`**.
    Derive it from a **verified server-side auth source** — the subject/user id from your auth provider
    (Clerk, Auth.js/NextAuth, Supabase Auth, Auth0, …) — and **never from a client-supplied header,
    query param, or body** (read it from the session in your route). A chat can't be read or
    overwritten under a different `userId`.
  </Accordion>
</AccordionGroup>

## How to add agent memory with the AI SDK

`recall_memory` and `save_memory` tools so the model reads and writes its own long-term memory.

```ts
import { createMemoryTools } from "@upstash/agentkit-ai-sdk";
import { generateText, stepCountIs } from "ai";

const tools = createMemoryTools({ userId });

await generateText({ model, tools, stopWhen: stepCountIs(5), prompt: "What do you know about me?" });
```

<Accordion title="Options and the userId tenant boundary">
  * **`userId`** _(required)_ — a string, or `(input, options) => string`.
  * `redis` — defaults to `Redis.fromEnv()`.
  * `topK` — max memories `recall` returns.
  * `minScore` — BM25 relevance floor.
  * `recallToolName` / `saveToolName` — override the tool names.

  `userId` is the only tenant boundary (required, non-empty, no `:`). Derive it from a **verified
  server-side auth source** (Clerk, Auth.js/NextAuth, Supabase Auth, Auth0, …) — never a
  client-supplied value. Memories are stored at `agentkit:memory:<userId>:<id>`.
</Accordion>

## How to add RAG with the AI SDK

`search` / `aggregate` / `count` tools over an Upstash Redis Search index; the model-facing
descriptions are generated from your schema.

```ts
import { s } from "@upstash/redis";
import { createSearchTools } from "@upstash/agentkit-ai-sdk";
import { generateText, stepCountIs } from "ai";

const schema = s.object({ name: s.string(), age: s.number(), city: s.string().noTokenize() });
const tools = createSearchTools({ schema, indexName: "users" });

await generateText({ model, tools, stopWhen: stepCountIs(5), prompt: "How many users named Ada live in London?" });
```

<Accordion title="Options">
  * **`schema`** _(required)_ — built with `s` from `@upstash/redis`.
  * `redis` — defaults to `Redis.fromEnv()`.
  * `indexName` — defaults to `"agentkit:search"`.
  * `prefix` — key prefix for indexed JSON docs (defaults to `"<indexName>:"`).
  * `defaultLimit` — default page size for `search` (10).

  The index is created (and `waitIndexing`-ed) reactively on first use — no setup step.
</Accordion>

## How to add rate limiting with the AI SDK

A configured [Upstash Ratelimit](/docs/redis/sdks/ratelimit-ts/overview). Call `.limit(identifier)` before
the model and short-circuit when over the limit.

```ts
import { createRateLimit, Ratelimit } from "@upstash/agentkit-ai-sdk";

const ratelimit = createRateLimit({ limiter: Ratelimit.slidingWindow(20, "1 m") });

const { success } = await ratelimit.limit(userId);
if (!success) throw new Error("rate limited"); // or return a 429 from your route
```

<Accordion title="Options">
  * **`limiter`** _(required)_ — e.g. `Ratelimit.slidingWindow(20, "1 m")` or `fixedWindow(...)`.
  * `redis` — defaults to `Redis.fromEnv()`.
  * `prefix` — base key prefix; keys are `<prefix>:<identifier>` (default `agentkit:rateLimit`).

  There's no model wrapper. Pass a per-user `identifier` to `.limit()` to throttle per user.
</Accordion>

## How to cache tools with the AI SDK

Memoize a map of AI SDK tools' results in Redis. Each tool is cached under its map key, scoped to
`userId`.

```ts
import { z } from "zod";
import { generateText, tool } from "ai";
import { cachedTools } from "@upstash/agentkit-ai-sdk";

const tools = cachedTools(
  {
    getWeather: tool({
      description: "Get the weather for a city",
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => fetchWeather(city),
    }),
  },
  { userId },
);

await generateText({ model, tools, prompt: "What's the weather in Paris?" });
```

<Accordion title="Options">
  Pass tools built with the AI SDK's `tool()` (so each keeps full input/output inference). Second arg:

  * **`userId`** _(required)_ — a string, or `(input, options) => string`; scopes every entry to this user.
  * `redis` — defaults to `Redis.fromEnv()`.
  * `ttlSeconds` — default per-result TTL for every tool.

  Cache keys are `agentkit:toolCache:<userId>:<toolName>:<hash-of-input>` — the `toolName` is the map
  key, so you never pass a name yourself.
</Accordion>

## How to put it all together with the AI SDK

A single `streamText` route can wire every feature: rate limit first, then memory, search, and cached
tools, persisting the whole conversation in `onFinish`:

```ts
// app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  stepCountIs,
  toUIMessageStream,
  tool,
  type UIMessage,
} from "ai";
import { z } from "zod";
import { s, Redis } from "@upstash/redis";
import {
  Ratelimit,
  cachedTools,
  createChatHistory,
  createMemoryTools,
  createRateLimit,
  createSearchTools,
} from "@upstash/agentkit-ai-sdk";

export async function POST(req: Request) {
  const { id, messages } = (await req.json()) as { id: string; messages: UIMessage[] };
  const redis = Redis.fromEnv();
  // Derive this from your verified auth session in production (Clerk, Auth.js, …), never a client value.
  const userId = "user-123";

  // 1. Rate limit (by user) before any model work.
  const ratelimit = createRateLimit({ redis, limiter: Ratelimit.slidingWindow(30, "1 m") });
  const { success } = await ratelimit.limit(userId);
  if (!success) return new Response("Rate limited", { status: 429 });

  // 2. Memory, search, and cached tools — all scoped to this user.
  const tools = {
    ...createMemoryTools({ redis, userId }),
    ...createSearchTools({ schema: s.object({ title: s.string(), author: s.string() }), redis, indexName: "books" }),
    ...cachedTools(
      {
        convert_price: tool({
          description: "Convert a USD price to another currency.",
          inputSchema: z.object({ usd: z.number(), currency: z.string() }),
          execute: async ({ usd, currency }) => ({ currency, amount: usd * 0.92 }),
        }),
      },
      { userId, redis },
    ),
  };

  const result = streamText({
    model: openai("gpt-5.4-mini"),
    messages: await convertToModelMessages(messages),
    tools,
    stopWhen: stepCountIs(5),
  });

  // 3. Persist the whole conversation when the stream finishes.
  const history = createChatHistory({ redis });
  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      originalMessages: messages,
      onFinish: ({ messages }) => history.saveChat({ userId, sessionId: id, messages, title: "New chat" }),
    }),
  });
}
```

A complete, runnable Next.js demo (`useChat` UI, chat sidebar with fuzzy search, inline tool calls)
lives in [`examples/ai-sdk-demo`](https://github.com/upstash/agentkit/tree/main/examples/ai-sdk-demo).

<CardGroup cols={2}>
  <Card title="AgentKit on GitHub" icon="github" href="https://github.com/upstash/agentkit">
    Source, packages, and the full example apps.
  </Card>
  <Card title="Vercel AI SDK" icon="up-right-from-square" href="https://ai-sdk.dev">
    The AI SDK this adapter plugs into.
  </Card>
</CardGroup>

# Vercel AI SDK Memory, RAG & Chat History with Redis
Source: https://upstash.com/docs/redis/sdks/agentkit/ai-sdk

[Upstash AgentKit](https://github.com/upstash/agentkit) builds AI agents on Upstash Redis: memory,
conversation history, caching, and RAG, with no separate vector database. The semantic features run on
[Upstash Redis Search](/docs/redis/search/introduction) and its `$smart` fuzzy operator.

`@upstash/agentkit-ai-sdk` is the [Vercel AI SDK](https://ai-sdk.dev) adapter — drop-ins for
`generateText` / `streamText`. `redis` defaults to `Redis.fromEnv()`, so you import only from this
package.

| Import | Feature |
| --- | --- |
| `createChatHistory` | Durable chat history on Redis Search — save, list, and `$smart`-search a user's transcripts. |
| `createMemoryTools` | `recall_memory` + `save_memory` tools so the model reads and writes long-term memory. |
| `createSearchTools` | `search` / `aggregate` / `count` tools over a Redis Search index (this is how you do RAG). |
| `createRateLimit` | A configured Upstash Ratelimit to call before the model. |
| `cachedTools` | Memoize a map of AI SDK tools' results in Redis. |

```bash
npm install @upstash/agentkit-ai-sdk @upstash/redis ai
```

<Note>
  AgentKit reads `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN` from the environment by default.
  Pass your own `@upstash/redis` client as `redis` to any helper to override.
</Note>

## How to store chat history with the AI SDK

A Redis-backed `ChatHistory<UIMessage>`, the durable source of truth for your conversations. `userId`
comes from your auth session; `chatId` is the `useChat` id that the client posts. Save the full
transcript from your route's `onFinish`:

```ts
// app/api/chat/route.ts
import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream } from "ai";
import { createChatHistory } from "@upstash/agentkit-ai-sdk";

const history = createChatHistory();

export async function POST(req: Request) {
  const userId = await getSessionUserId(req); // your auth session, never a client-sent id
  const { id: chatId, messages } = await req.json(); // useChat posts its chat id + the full transcript

  const result = streamText({ model, messages: convertToModelMessages(messages) });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      originalMessages: messages,
      onFinish: ({ messages }) =>
        history.saveChat({ userId, sessionId: chatId, messages, title: "New chat" }),
    }),
  });
}
```

To load a chat, take `chatId` from the page route and `userId` from the session, then seed `useChat`:

```ts
const chat = await history.getChat({ userId, sessionId: chatId }); // full transcript, or null
const chats = await history.listChats({ userId, limit: 50 }); // summaries, no messages
const hits = await history.searchChats({ userId, query: "headphones", target: "both", limit: 20 });
// client: useChat({ id: chatId, messages: chat?.messages ?? [] })
```

<AccordionGroup>
  <Accordion title="Config and how it's stored">
    ```ts
    createChatHistory({
      redis, // optional: defaults to Redis.fromEnv()
      prefix: "agentkit:chat", // optional: base key prefix
      indexName: "agentkit_chat", // optional: index name (defaults to the prefix)
      ttlSeconds: 60 * 60 * 24 * 30, // optional: per-chat TTL (default: no expiry)
    });
    ```

    Each chat is one JSON doc at `agentkit:chat:<userId>:<sessionId>` (keyed per user, so two users
    can't collide on a `sessionId`), indexed over `userId` + `sessionId` (filters) and `userMessages` +
    `modelMessages` (`$smart` fuzzy text); the raw `messages` array rides along unindexed. `saveChat`
    overwrites the **whole** array (no delta merge) — `useChat` sends the full conversation. Other
    methods: `getChat` / `deleteChat` (`{ userId, sessionId }`), `listChats` / `searchChats`
    (`{ userId }`).
  </Accordion>
  <Accordion title="Security — userId is the tenant boundary">
    Every method takes a single object; `userId` is **required, non-empty, and may not contain `:`**.
    Derive it from a **verified server-side auth source** — the subject/user id from your auth provider
    (Clerk, Auth.js/NextAuth, Supabase Auth, Auth0, …) — and **never from a client-supplied header,
    query param, or body** (read it from the session in your route). A chat can't be read or
    overwritten under a different `userId`.
  </Accordion>
</AccordionGroup>

## How to add agent memory with the AI SDK

`recall_memory` and `save_memory` tools so the model reads and writes its own long-term memory.

```ts
import { createMemoryTools } from "@upstash/agentkit-ai-sdk";
import { generateText, stepCountIs } from "ai";

const tools = createMemoryTools({ userId });

await generateText({ model, tools, stopWhen: stepCountIs(5), prompt: "What do you know about me?" });
```

<Accordion title="Options and the userId tenant boundary">
  * **`userId`** _(required)_ — a string, or `(input, options) => string`.
  * `redis` — defaults to `Redis.fromEnv()`.
  * `topK` — max memories `recall` returns.
  * `minScore` — BM25 relevance floor.
  * `recallToolName` / `saveToolName` — override the tool names.

  `userId` is the only tenant boundary (required, non-empty, no `:`). Derive it from a **verified
  server-side auth source** (Clerk, Auth.js/NextAuth, Supabase Auth, Auth0, …) — never a
  client-supplied value. Memories are stored at `agentkit:memory:<userId>:<id>`.
</Accordion>

## How to add RAG with the AI SDK

`search` / `aggregate` / `count` tools over an Upstash Redis Search index; the model-facing
descriptions are generated from your schema.

```ts
import { s } from "@upstash/redis";
import { createSearchTools } from "@upstash/agentkit-ai-sdk";
import { generateText, stepCountIs } from "ai";

const schema = s.object({ name: s.string(), age: s.number(), city: s.string().noTokenize() });
const tools = createSearchTools({ schema, indexName: "users" });

await generateText({ model, tools, stopWhen: stepCountIs(5), prompt: "How many users named Ada live in London?" });
```

<Accordion title="Options">
  * **`schema`** _(required)_ — built with `s` from `@upstash/redis`.
  * `redis` — defaults to `Redis.fromEnv()`.
  * `indexName` — defaults to `"agentkit:search"`.
  * `prefix` — key prefix for indexed JSON docs (defaults to `"<indexName>:"`).
  * `defaultLimit` — default page size for `search` (10).

  The index is created (and `waitIndexing`-ed) reactively on first use — no setup step.
</Accordion>

## How to add rate limiting with the AI SDK

A configured [Upstash Ratelimit](/docs/redis/sdks/ratelimit-ts/overview). Call `.limit(identifier)` before
the model and short-circuit when over the limit.

```ts
import { createRateLimit, Ratelimit } from "@upstash/agentkit-ai-sdk";

const ratelimit = createRateLimit({ limiter: Ratelimit.slidingWindow(20, "1 m") });

const { success } = await ratelimit.limit(userId);
if (!success) throw new Error("rate limited"); // or return a 429 from your route
```

<Accordion title="Options">
  * **`limiter`** _(required)_ — e.g. `Ratelimit.slidingWindow(20, "1 m")` or `fixedWindow(...)`.
  * `redis` — defaults to `Redis.fromEnv()`.
  * `prefix` — base key prefix; keys are `<prefix>:<identifier>` (default `agentkit:rateLimit`).

  There's no model wrapper. Pass a per-user `identifier` to `.limit()` to throttle per user.
</Accordion>

## How to cache tools with the AI SDK

Memoize a map of AI SDK tools' results in Redis. Each tool is cached under its map key, scoped to
`userId`.

```ts
import { z } from "zod";
import { generateText, tool } from "ai";
import { cachedTools } from "@upstash/agentkit-ai-sdk";

const tools = cachedTools(
  {
    getWeather: tool({
      description: "Get the weather for a city",
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => fetchWeather(city),
    }),
  },
  { userId },
);

await generateText({ model, tools, prompt: "What's the weather in Paris?" });
```

<Accordion title="Options">
  Pass tools built with the AI SDK's `tool()` (so each keeps full input/output inference). Second arg:

  * **`userId`** _(required)_ — a string, or `(input, options) => string`; scopes every entry to this user.
  * `redis` — defaults to `Redis.fromEnv()`.
  * `ttlSeconds` — default per-result TTL for every tool.

  Cache keys are `agentkit:toolCache:<userId>:<toolName>:<hash-of-input>` — the `toolName` is the map
  key, so you never pass a name yourself.
</Accordion>

## How to put it all together with the AI SDK

A single `streamText` route can wire every feature: rate limit first, then memory, search, and cached
tools, persisting the whole conversation in `onFinish`:

```ts
// app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  stepCountIs,
  toUIMessageStream,
  tool,
  type UIMessage,
} from "ai";
import { z } from "zod";
import { s, Redis } from "@upstash/redis";
import {
  Ratelimit,
  cachedTools,
  createChatHistory,
  createMemoryTools,
  createRateLimit,
  createSearchTools,
} from "@upstash/agentkit-ai-sdk";

export async function POST(req: Request) {
  const { id, messages } = (await req.json()) as { id: string; messages: UIMessage[] };
  const redis = Redis.fromEnv();
  // Derive this from your verified auth session in production (Clerk, Auth.js, …), never a client value.
  const userId = "user-123";

  // 1. Rate limit (by user) before any model work.
  const ratelimit = createRateLimit({ redis, limiter: Ratelimit.slidingWindow(30, "1 m") });
  const { success } = await ratelimit.limit(userId);
  if (!success) return new Response("Rate limited", { status: 429 });

  // 2. Memory, search, and cached tools — all scoped to this user.
  const tools = {
    ...createMemoryTools({ redis, userId }),
    ...createSearchTools({ schema: s.object({ title: s.string(), author: s.string() }), redis, indexName: "books" }),
    ...cachedTools(
      {
        convert_price: tool({
          description: "Convert a USD price to another currency.",
          inputSchema: z.object({ usd: z.number(), currency: z.string() }),
          execute: async ({ usd, currency }) => ({ currency, amount: usd * 0.92 }),
        }),
      },
      { userId, redis },
    ),
  };

  const result = streamText({
    model: openai("gpt-5.4-mini"),
    messages: await convertToModelMessages(messages),
    tools,
    stopWhen: stepCountIs(5),
  });

  // 3. Persist the whole conversation when the stream finishes.
  const history = createChatHistory({ redis });
  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      originalMessages: messages,
      onFinish: ({ messages }) => history.saveChat({ userId, sessionId: id, messages, title: "New chat" }),
    }),
  });
}
```

A complete, runnable Next.js demo (`useChat` UI, chat sidebar with fuzzy search, inline tool calls)
lives in [`examples/ai-sdk-demo`](https://github.com/upstash/agentkit/tree/main/examples/ai-sdk-demo).

<CardGroup cols={2}>
  <Card title="AgentKit on GitHub" icon="github" href="https://github.com/upstash/agentkit">
    Source, packages, and the full example apps.
  </Card>
  <Card title="Vercel AI SDK" icon="up-right-from-square" href="https://ai-sdk.dev">
    The AI SDK this adapter plugs into.
  </Card>
</CardGroup>

# Memory, Chat History, RAG, Rate Limiting & Sandboxes for the Vercel Eve Agent Framework
Source: https://upstash.com/docs/redis/sdks/agentkit/eve

[Upstash AgentKit](https://github.com/upstash/agentkit) builds AI agents on Upstash Redis: memory,
conversation history, caching, and RAG, with no separate vector database. The semantic features run on
[Upstash Redis Search](/docs/redis/search/introduction) and its `$smart` fuzzy operator.

Two packages bring AgentKit to **Eve, the Vercel agent framework**. They work together in one agent:

| Package | What it is |
| --- | --- |
| `@upstash/agentkit-eve-extension` | An [Eve extension](https://eve.dev/docs/extensions): one mount file adds memory, searchable chat history, and RAG. |
| `@upstash/agentkit-eve` | Per-file building blocks, plus the two things an extension cannot contribute: rate limiting and a sandbox backend. |

Mount the extension for the bundled setup. Add the package when you need a rate-limit gate, an Upstash
Box sandbox, or control over an individual tool file.

Start from an eve project (0.25.2 or later). Scaffold one, which installs `eve` and an AI-SDK provider
for you:

```bash
npx eve@latest init my-agent
# or, to start with a Next.js app:
npx eve@latest init my-agent --channel-web-nextjs
```

<Note>
  AgentKit reads `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN` from the environment by default.
  Pass your own `@upstash/redis` client as `redis` to any helper to override.
</Note>

## The Eve extension: memory, chat history, and RAG

Everything in this part comes from `@upstash/agentkit-eve-extension`, configured in one mount file.

### How to mount the Upstash extension in Vercel Eve

```bash
npm install @upstash/agentkit-eve-extension
```

One file under `agent/extensions/` mounts everything. Every config field is optional, and the smallest
mount gives the model long-term memory:

```ts
// agent/extensions/agentkit.ts
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit();
```

The filename supplies the namespace, so contributions compose as `agentkit__recall_memory`,
`agentkit__save_memory`, and so on. The extension also merges a short instructions fragment into your
system prompt telling the model when to save and recall.

The three feature groups below are independent of each other. Memory holds facts the model decided to
keep. Chat history is the transcript of what was said. Search is retrieval over documents you write
into your own index. Each has its own Redis keyspace and its own search index.

### How to add memory to Vercel Eve

Long-term memory the model reads and writes itself. A bare mount already has it; the `memory` field
only tunes recall:

```ts
// agent/extensions/agentkit.ts
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit({
  memory: { topK: 5, minScore: 1 },
});
```

<Accordion title="Tools, options, and the userId tenant boundary">
  * `agentkit__recall_memory` — searches the user's memories; called with no `query` it returns all of them.
  * `agentkit__save_memory` — stores one durable fact about the user.
  * `memory.topK` — max memories a recall returns.
  * `memory.minScore` — relevance floor. Scores are unbounded BM25 values, not `[0,1]`.

  `userId` is the only tenant boundary. It defaults to Eve's verified session auth
  (`auth.current?.principalId`, then `auth.initiator?.principalId`, then the session id), so
  configure a real authenticator (`vercelOidc()`, an OIDC/JWT provider like Clerk, …) if you want the
  principal to be trustworthy. You can also set it to a string, which puts every caller in one shared
  scope, or derive it per call:

  ```ts
  export default agentkit({
    userId: (ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
  });
  ```

  Memories are stored at `agentkit:memory:<userId>:<id>`.
</Accordion>

### How to add searchable chat history to Vercel Eve

`chatHistory: true` persists every user and assistant message to Redis as the session streams, and
gives the model two tools over that store. A user can then ask about something settled in a previous
conversation:

```ts
// agent/extensions/agentkit.ts
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit({ chatHistory: true });
```

* `agentkit__search_chat_history` runs a `$smart` (typo-tolerant) search over what was said and returns
  the matching chats as summaries: `sessionId`, `title`, `updatedAt`, `messageCount`, `score`. The
  current conversation is excluded, since it's already in context.
* `agentkit__read_chat_history` reads one of those chats back by `sessionId`, newest messages last.

Both tools take `userId` from the session, so the model cannot widen a lookup past the current user's
own transcripts.

Your own code reads the same store with `ChatHistory` from `@upstash/agentkit-sdk`, which is how you'd
build a history sidebar or run evals over past sessions:

```bash
npm install @upstash/agentkit-sdk @upstash/redis
```

```ts
// app/api/chats/route.ts — server-side only; `userId` comes from your session, never the client
import { Redis } from "@upstash/redis";
import { ChatHistory } from "@upstash/agentkit-sdk";

// `ChatHistory` is generic over the message type, and the extension doesn't export the shape its
// hook writes — declare it to get a typed `messages` array back.
type StoredMessage = { role: "user" | "assistant"; content: string; createdAt: number };

// Pass the same prefix / indexName / ttlSeconds you set on `chatHistory` in the mount file.
const history = new ChatHistory<StoredMessage>({ redis: Redis.fromEnv() });

// List: summaries only (no messages), newest-updated first.
const chats = await history.listChats({ userId, limit: 50 });

// Search: `$smart` fuzzy match, summaries plus a BM25 `score`. target: "user" | "model" | "both".
const hits = await history.searchChats({ userId, query: "the retry schema", target: "both", limit: 20 });

// Read: one full transcript, or null if this user has no chat with that sessionId.
const chat = await history.getChat({ userId, sessionId: hits[0].sessionId });
chat?.messages.forEach((m) => console.log(m.role, m.content));
```

`userId` must be the same value the extension resolved for those sessions, and both ids are Redis key
parts, so `:` in a derived value is replaced with `_`. `deleteChat({ userId, sessionId })` removes a
chat and its index entry.

<Accordion title="Options and storage">
  Pass an object in place of `true` to tune storage:

  * `chatHistory.prefix` — base key prefix (default `agentkit:chat`).
  * `chatHistory.indexName` — [Redis Search](/docs/redis/search/introduction) index name (defaults to the
    identifier-safe `prefix`).
  * `chatHistory.ttlSeconds` — per-chat TTL. Omit for no expiry.

  Each session is one JSON document at `agentkit:chat:<userId>:<sessionId>`, holding the raw transcript
  plus `$smart`-indexed user and model text. A search returns summaries, and a read is capped at 50
  messages per call with a `truncated` flag, so neither can flood the context window.

  Redis is the durable record here, since Eve's own workflow store is pruned after a run completes.

  These tools look history up on demand. They don't resume a session: Eve does that through its own
  session cursor.
</Accordion>

### How to add RAG to Vercel Eve

Point the extension at an [Upstash Redis Search](/docs/redis/search/introduction) index and the model gets
`search`, `search_aggregate`, and `search_count` tools over it. You build the schema with `s` from
`@upstash/redis`, so your mount file imports it. Add the package to your app:

```bash
npm install @upstash/redis
```

```ts
// agent/extensions/agentkit.ts
import { s } from "@upstash/redis";
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit({
  search: {
    schema: s.object({ title: s.string(), author: s.string().noTokenize(), year: s.number() }),
    indexName: "books",
  },
});
```

<Accordion title="Options and how the tools are described to the model">
  * **`search.schema`** _(required)_ — built with `s` from `@upstash/redis`.
  * `search.indexName` — defaults to `"agentkit:search"`; ties all three tools to one index.
  * `search.prefix` — key prefix for indexed JSON docs (defaults to `"<indexName>:"`).
  * `search.defaultLimit` — default page size for `search` (10).

  Tool descriptions are generated from your schema (field names, types, and the filter operators that
  apply to each), so the model learns the index without any prompt text from you. You write the
  documents yourself with `redis.json.set` under the prefix, and the index is created on first read.

  Omit `search` and these three tools don't exist at all. Like the chat-history tools, they resolve at
  session start, which is why they don't appear in a static tool listing.
</Accordion>

### Extension configuration reference

```ts
// agent/extensions/agentkit.ts
import { s } from "@upstash/redis";
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit({
  // optional: string, or (ctx) => string. Defaults to the verified principal, then the session id.
  userId: (ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
  // optional: an explicit client; defaults to Redis.fromEnv()
  // redis: new Redis({ url, token }),
  // optional: tune memory recall
  memory: { topK: 5, minScore: 1 },
  // optional: omit and the search tools don't exist
  search: {
    schema: s.object({ title: s.string(), author: s.string().noTokenize(), year: s.number() }),
    indexName: "books",
    prefix: "books:", // optional
    defaultLimit: 10, // optional
  },
  // optional: off by default. `true`, or an object to tune storage
  chatHistory: { ttlSeconds: 60 * 60 * 24 * 30 },
});
```

<Accordion title="Dropping or overriding a contribution">
  Mount as a directory and override a slot by filename. This is how you drop a tool you don't want,
  for example to capture chat history without letting the model read it back:

  ```
  agent/extensions/agentkit/
    extension.ts                     # the mount: export default agentkit({ ... })
    tools/read_chat_history.ts       # your override for agentkit__read_chat_history
  ```

  ```ts
  // agent/extensions/agentkit/tools/read_chat_history.ts
  import { disableTool } from "eve/tools";

  export default disableTool();
  ```

  You can also re-define the memory tools, say to gate saves behind approval, by importing them from
  `@upstash/agentkit-eve-extension/tools` and spreading them into your own `defineTool`.
</Accordion>

<Note>
  `@upstash/agentkit-eve` below offers memory and RAG as standalone tool files too. Use those when you
  want to configure one tool at a time, and use the package alongside the extension for rate limiting
  and sandboxes.
</Note>

## The Eve package: rate limiting, sandboxes, and tool files

Everything in this part comes from `@upstash/agentkit-eve`, written as individual `agent/` files.
Rate limiting and the sandbox backend live here because an extension cannot contribute a channel or
a sandbox.

### How to add rate limiting to Vercel Eve

```bash
npm install @upstash/agentkit-eve @upstash/redis
```

`createRateLimitAuth` is a ready `AuthFn` that throttles inbound requests. Drop it into your channel's
[auth walk](https://eve.dev/docs/guides/auth-and-route-protection) ahead of your real authenticators.

```ts
// agent/channels/eve.ts
import { createRateLimitAuth, Ratelimit } from "@upstash/agentkit-eve";
import { localDev, vercelOidc } from "eve/channels/auth";
import { eveChannel } from "eve/channels/eve";

export default eveChannel({
  auth: [
    createRateLimitAuth({
      limiter: Ratelimit.slidingWindow(20, "1 m"),
      identifier: (req) => req.headers.get("x-forwarded-for") ?? "anonymous",
    }),
    localDev(),
    vercelOidc(),
  ],
});
```

<AccordionGroup>
  <Accordion title="Options and the required identifier">
    * **`limiter`** _(required)_ — e.g. `Ratelimit.slidingWindow(20, "1 m")` or `fixedWindow(...)`.
    * **`identifier`** _(required)_ — a string, or `(request) => string`. There's no implicit `"global"`:
      one shared bucket lets a single abusive caller exhaust the window for everyone, so derive it per
      request (an auth user id, an API key, or `x-forwarded-for` for per-IP).
    * `prefix` — base key prefix; keys are `<prefix>:<identifier>` (default `agentkit:rateLimit`).
    * `message` — 403 body when over the limit.
    * `redis` — defaults to `Redis.fromEnv()`.

    It's a gate: under the limit it returns `null` to fall through to the next `AuthFn`; over it throws
    a 403.
  </Accordion>
  <Accordion title="Why only POST requests are counted">
    Eve runs each turn as two authenticated requests: the message `POST` (which invokes the model) and a
    follow-up `GET …/stream` that opens the reply stream. The auth walk runs on both, so counting both
    would charge every turn twice. `createRateLimitAuth` counts only the `POST`s, so one turn costs one
    token: a `Ratelimit.slidingWindow(20, "1 m")` allows 20 turns per minute, not 10. The session-read
    `GET`s pass through unthrottled.
  </Accordion>
</AccordionGroup>

### How to add a sandbox to Vercel Eve

A drop-in replacement for Eve's `vercel()` backend, powered by
[Upstash Box](https://github.com/upstash/box). Swap the import and keep the rest of your
[sandbox file](https://eve.dev/docs/sandbox) the same.

```bash
npm install @upstash/box
```

```ts
// agent/sandbox.ts
import { defineSandbox } from "eve/sandbox";
import { upstash } from "@upstash/agentkit-eve/sandbox"; // was: eve/sandbox/vercel

export default defineSandbox({
  backend: upstash({ runtime: "node", size: "medium" }),
  revalidationKey: () => "repo-bootstrap-v1",
  async bootstrap({ use }) {
    const sandbox = await use({ networkPolicy: "allow-all" }); // open egress to install packages
    await sandbox.run({ command: "apt-get install -y jq" });
  },
  async onSession({ use }) {
    await use(); // inherits the secure deny-all default
  },
});
```

<AccordionGroup>
  <Accordion title="Config: Box's BoxConfig">
    `upstash(config)` takes the `@upstash/box` `BoxConfig` verbatim, meaning whatever you'd pass to
    `Box.create({...})`: `runtime`, `size`, `apiKey` (defaults to `UPSTASH_BOX_API_KEY`), `keepAlive`,
    `initCommand`, `env`, `skills`, `mcpServers`, `timeout`, and so on. It also takes an optional
    `redis` (defaults to `Redis.fromEnv()`). `networkPolicy` is **not** a config knob (see below).
    `@upstash/box` is an optional peer dependency, needed only when you import
    `@upstash/agentkit-eve/sandbox`.
  </Accordion>
  <Accordion title="Security: network egress is deny-all by default">
    The sandbox runs untrusted, model-generated code, so open egress would mean SSRF / data
    exfiltration / reaching your own infrastructure from inside the box. Open it per-session, in
    `bootstrap`'s `use(...)` or the session `use(...)`, and never as a config knob. Note that `env` passed
    to `upstash({ env })` is readable by code running in the box; don't pass secrets you wouldn't want
    it to see.
  </Accordion>
  <Accordion title="Brokering credentials (injecting headers)">
    Box network policies are plain domain/CIDR allow-lists. Eve's per-domain firewall rules (`transform`
    header injection, `forwardURL`) have no Box equivalent, so passing them in `use({ networkPolicy })`
    **throws** rather than silently sending the request unauthenticated:

    ```ts
    // ❌ throws — Box can't inject headers via a per-session policy
    export default defineSandbox({
      backend: upstash({ runtime: "node" }),
      async onSession({ use }) {
        await use({
          networkPolicy: {
            allow: { "api.example.com": [{ transform: [{ headers: { authorization: "Bearer …" } }] }] },
          },
        });
      },
    });
    ```

    Broker credentials with Box's `attachHeaders` instead (set at backend creation; a proxy on the box
    injects them), and open the domain with a plain allow-list:

    ```ts
    // ✅ headers injected at the firewall; the secret never enters the box
    export default defineSandbox({
      backend: upstash({
        runtime: "node",
        attachHeaders: { "api.example.com": { Authorization: "Bearer …" } },
      }),
      async onSession({ use }) {
        await use({ networkPolicy: { allow: ["api.example.com"] } });
      },
    });
    ```
  </Accordion>
  <Accordion title="Lifecycle: one box per conversation">
    **Reuse.** Eve re-opens a session several times per turn, and the backend reattaches to the same Box
    instead of creating a new one each time. Boxes default to Box's pause-based idle lifecycle
    (`keepAlive: false`): auto-paused when idle, resumed on reattach, reaped by Box. Pass
    `keepAlive: true` only for an always-running box you manage yourself.

    **Template registry.** Eve builds your template (seed files + `bootstrap`) at build/startup, but
    session creation runs per request in a different process, so the snapshot id is stored in a durable
    Redis registry (`redis`, defaulting to `Redis.fromEnv()`). Eve roots its tools at `/workspace` while
    a Box session lives at `/workspace/home`; the backend bridges the two automatically.
  </Accordion>
</AccordionGroup>

### How to cache tools in Vercel Eve

Like Eve's `defineTool`, but the `execute` result is memoized in Redis.

```ts
// agent/tools/get_weather.ts
import { z } from "zod";
import { defineCachedTool } from "@upstash/agentkit-eve";

export default defineCachedTool({
  description: "Get the current weather for a city.",
  inputSchema: z.object({ city: z.string() }),
  execute: async ({ city }) => fetchWeather(city),
  toolName: "get_weather",
  userId: (_, ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
});
```

<Accordion title="Options">
  * `description` / `inputSchema` / `execute` — the usual `defineTool` fields; `execute`'s result is memoized.
  * **`toolName`** _(required)_ — the tool segment of the cache key.
  * **`userId`** _(required)_ — a string, or `(input, ctx) => string`; scopes the cache per user.
  * `ttlSeconds` — per-result TTL (default: no expiry).
  * `redis` — defaults to `Redis.fromEnv()`.

  Keys are `agentkit:toolCache:<userId>:<toolName>:<hash>`.
</Accordion>

### Memory and RAG as individual tool files

The same two features the extension mounts, written as standalone `agent/tools/` files. Use these when
you want to configure each tool on its own.

Memory takes one file per tool:

```ts
// agent/tools/recall_memory.ts
import { defineMemoryRecallTool } from "@upstash/agentkit-eve";

export default defineMemoryRecallTool({
  userId: (_, ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
});
```

```ts
// agent/tools/save_memory.ts
import { defineMemorySaveTool } from "@upstash/agentkit-eve";

export default defineMemorySaveTool({
  userId: (_, ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
});
```

RAG is `defineSearchTools`, the counterpart to the
[AI SDK adapter's](/docs/redis/sdks/agentkit/ai-sdk#how-to-add-rag-with-the-ai-sdk) `createSearchTools`:

```ts
// agent/tools/search_books.ts
import { s } from "@upstash/redis";
import { defineSearchTools } from "@upstash/agentkit-eve";

export default defineSearchTools({
  schema: s.object({ title: s.string(), author: s.string().noTokenize(), year: s.number() }),
  indexName: "books",
}).search; // aggregate_books.ts → .aggregate, count_books.ts → .count
```

<Accordion title="Options and the one-file-per-tool rule">
  `defineMemoryRecallTool` / `defineMemorySaveTool` take a required `userId` (string or
  `(input, ctx) => string`), plus `topK`, `minScore`, and `redis`. `defineSearchTools` takes a required
  `schema`, plus `indexName`, `prefix`, `defaultLimit`, and `redis`.

  Each tool file must be self-contained, so call `defineSearchTools` in each one and export the member
  you want, repeating the same `schema` and `indexName` across `search_books.ts`, `aggregate_books.ts`,
  and `count_books.ts`. The index is created on first use, and every returned tool is already
  `defineTool`-branded.

  This package has no chat history. It comes from the extension, or from
  [`ChatHistory` in `@upstash/agentkit-sdk`](#how-to-add-searchable-chat-history-to-vercel-eve) if
  you're writing the code yourself.
</Accordion>

## Working with Eve's `agent/` files

Eve's runtime snapshots each tool/channel/hook file and resolves only **package** imports from it. It
does **not** include shared `agent/`-source modules such as an `agent/lib/redis.ts`. So inside `agent/`:

* Import only from packages, never from other `agent/` files.
* Lean on the defaults. `redis` falls back to `Redis.fromEnv()` in every helper, so you almost never pass it.
* Repeat config (schema, names) in each file instead of sharing a module.

Shared app code, like a seeder a page calls, belongs in your project `lib/` and is imported by the app,
not by `agent/` files. Extensions are exempt from all of this. The extension package ships as one
compiled unit, which is why its whole configuration fits in a single mount file.

## How to run the Vercel Eve example apps

Two complete `eve` apps live in the AgentKit repo:

* [`examples/eve-extension-demo`](https://github.com/upstash/agentkit/tree/main/examples/eve-extension-demo)
  is a minimal agent whose whole configuration is one extension mount, with memory, chat history, and
  book search turned on.
* [`examples/eve-demo`](https://github.com/upstash/agentkit/tree/main/examples/eve-demo) uses the
  file-by-file package (memory, search, cached tools, a rate-limit gate, and an Upstash Box sandbox),
  with a chat UI that renders tool calls inline.

<CardGroup cols={2}>
  <Card title="AgentKit on GitHub" icon="github" href="https://github.com/upstash/agentkit">
    Source, packages, and the full example apps.
  </Card>
  <Card title="Eve" icon="up-right-from-square" href="https://eve.dev">
    The Vercel agent framework this adapter targets.
  </Card>
</CardGroup>

# Memory, Chat History, RAG, Rate Limiting & Sandboxes for the Vercel Eve Agent Framework
Source: https://upstash.com/docs/redis/sdks/agentkit/eve

[Upstash AgentKit](https://github.com/upstash/agentkit) builds AI agents on Upstash Redis: memory,
conversation history, caching, and RAG, with no separate vector database. The semantic features run on
[Upstash Redis Search](/docs/redis/search/introduction) and its `$smart` fuzzy operator.

Two packages bring AgentKit to **Eve, the Vercel agent framework**. They work together in one agent:

| Package | What it is |
| --- | --- |
| `@upstash/agentkit-eve-extension` | An [Eve extension](https://eve.dev/docs/extensions): one mount file adds memory, searchable chat history, and RAG. |
| `@upstash/agentkit-eve` | Per-file building blocks, plus the two things an extension cannot contribute: rate limiting and a sandbox backend. |

Mount the extension for the bundled setup. Add the package when you need a rate-limit gate, an Upstash
Box sandbox, or control over an individual tool file.

Start from an eve project (0.25.2 or later). Scaffold one, which installs `eve` and an AI-SDK provider
for you:

```bash
npx eve@latest init my-agent
# or, to start with a Next.js app:
npx eve@latest init my-agent --channel-web-nextjs
```

<Note>
  AgentKit reads `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN` from the environment by default.
  Pass your own `@upstash/redis` client as `redis` to any helper to override.
</Note>

## The Eve extension: memory, chat history, and RAG

Everything in this part comes from `@upstash/agentkit-eve-extension`, configured in one mount file.

### How to mount the Upstash extension in Vercel Eve

```bash
npm install @upstash/agentkit-eve-extension
```

One file under `agent/extensions/` mounts everything. Every config field is optional, and the smallest
mount gives the model long-term memory:

```ts
// agent/extensions/agentkit.ts
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit();
```

The filename supplies the namespace, so contributions compose as `agentkit__recall_memory`,
`agentkit__save_memory`, and so on. The extension also merges a short instructions fragment into your
system prompt telling the model when to save and recall.

The three feature groups below are independent of each other. Memory holds facts the model decided to
keep. Chat history is the transcript of what was said. Search is retrieval over documents you write
into your own index. Each has its own Redis keyspace and its own search index.

### How to add memory to Vercel Eve

Long-term memory the model reads and writes itself. A bare mount already has it; the `memory` field
only tunes recall:

```ts
// agent/extensions/agentkit.ts
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit({
  memory: { topK: 5, minScore: 1 },
});
```

<Accordion title="Tools, options, and the userId tenant boundary">
  * `agentkit__recall_memory` — searches the user's memories; called with no `query` it returns all of them.
  * `agentkit__save_memory` — stores one durable fact about the user.
  * `memory.topK` — max memories a recall returns.
  * `memory.minScore` — relevance floor. Scores are unbounded BM25 values, not `[0,1]`.

  `userId` is the only tenant boundary. It defaults to Eve's verified session auth
  (`auth.current?.principalId`, then `auth.initiator?.principalId`, then the session id), so
  configure a real authenticator (`vercelOidc()`, an OIDC/JWT provider like Clerk, …) if you want the
  principal to be trustworthy. You can also set it to a string, which puts every caller in one shared
  scope, or derive it per call:

  ```ts
  export default agentkit({
    userId: (ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
  });
  ```

  Memories are stored at `agentkit:memory:<userId>:<id>`.
</Accordion>

### How to add searchable chat history to Vercel Eve

`chatHistory: true` persists every user and assistant message to Redis as the session streams, and
gives the model two tools over that store. A user can then ask about something settled in a previous
conversation:

```ts
// agent/extensions/agentkit.ts
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit({ chatHistory: true });
```

* `agentkit__search_chat_history` runs a `$smart` (typo-tolerant) search over what was said and returns
  the matching chats as summaries: `sessionId`, `title`, `updatedAt`, `messageCount`, `score`. The
  current conversation is excluded, since it's already in context.
* `agentkit__read_chat_history` reads one of those chats back by `sessionId`, newest messages last.

Both tools take `userId` from the session, so the model cannot widen a lookup past the current user's
own transcripts.

Your own code reads the same store with `ChatHistory` from `@upstash/agentkit-sdk`, which is how you'd
build a history sidebar or run evals over past sessions:

```bash
npm install @upstash/agentkit-sdk @upstash/redis
```

```ts
// app/api/chats/route.ts — server-side only; `userId` comes from your session, never the client
import { Redis } from "@upstash/redis";
import { ChatHistory } from "@upstash/agentkit-sdk";

// `ChatHistory` is generic over the message type, and the extension doesn't export the shape its
// hook writes — declare it to get a typed `messages` array back.
type StoredMessage = { role: "user" | "assistant"; content: string; createdAt: number };

// Pass the same prefix / indexName / ttlSeconds you set on `chatHistory` in the mount file.
const history = new ChatHistory<StoredMessage>({ redis: Redis.fromEnv() });

// List: summaries only (no messages), newest-updated first.
const chats = await history.listChats({ userId, limit: 50 });

// Search: `$smart` fuzzy match, summaries plus a BM25 `score`. target: "user" | "model" | "both".
const hits = await history.searchChats({ userId, query: "the retry schema", target: "both", limit: 20 });

// Read: one full transcript, or null if this user has no chat with that sessionId.
const chat = await history.getChat({ userId, sessionId: hits[0].sessionId });
chat?.messages.forEach((m) => console.log(m.role, m.content));
```

`userId` must be the same value the extension resolved for those sessions, and both ids are Redis key
parts, so `:` in a derived value is replaced with `_`. `deleteChat({ userId, sessionId })` removes a
chat and its index entry.

<Accordion title="Options and storage">
  Pass an object in place of `true` to tune storage:

  * `chatHistory.prefix` — base key prefix (default `agentkit:chat`).
  * `chatHistory.indexName` — [Redis Search](/docs/redis/search/introduction) index name (defaults to the
    identifier-safe `prefix`).
  * `chatHistory.ttlSeconds` — per-chat TTL. Omit for no expiry.

  Each session is one JSON document at `agentkit:chat:<userId>:<sessionId>`, holding the raw transcript
  plus `$smart`-indexed user and model text. A search returns summaries, and a read is capped at 50
  messages per call with a `truncated` flag, so neither can flood the context window.

  Redis is the durable record here, since Eve's own workflow store is pruned after a run completes.

  These tools look history up on demand. They don't resume a session: Eve does that through its own
  session cursor.
</Accordion>

### How to add RAG to Vercel Eve

Point the extension at an [Upstash Redis Search](/docs/redis/search/introduction) index and the model gets
`search`, `search_aggregate`, and `search_count` tools over it. You build the schema with `s` from
`@upstash/redis`, so your mount file imports it. Add the package to your app:

```bash
npm install @upstash/redis
```

```ts
// agent/extensions/agentkit.ts
import { s } from "@upstash/redis";
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit({
  search: {
    schema: s.object({ title: s.string(), author: s.string().noTokenize(), year: s.number() }),
    indexName: "books",
  },
});
```

<Accordion title="Options and how the tools are described to the model">
  * **`search.schema`** _(required)_ — built with `s` from `@upstash/redis`.
  * `search.indexName` — defaults to `"agentkit:search"`; ties all three tools to one index.
  * `search.prefix` — key prefix for indexed JSON docs (defaults to `"<indexName>:"`).
  * `search.defaultLimit` — default page size for `search` (10).

  Tool descriptions are generated from your schema (field names, types, and the filter operators that
  apply to each), so the model learns the index without any prompt text from you. You write the
  documents yourself with `redis.json.set` under the prefix, and the index is created on first read.

  Omit `search` and these three tools don't exist at all. Like the chat-history tools, they resolve at
  session start, which is why they don't appear in a static tool listing.
</Accordion>

### Extension configuration reference

```ts
// agent/extensions/agentkit.ts
import { s } from "@upstash/redis";
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit({
  // optional: string, or (ctx) => string. Defaults to the verified principal, then the session id.
  userId: (ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
  // optional: an explicit client; defaults to Redis.fromEnv()
  // redis: new Redis({ url, token }),
  // optional: tune memory recall
  memory: { topK: 5, minScore: 1 },
  // optional: omit and the search tools don't exist
  search: {
    schema: s.object({ title: s.string(), author: s.string().noTokenize(), year: s.number() }),
    indexName: "books",
    prefix: "books:", // optional
    defaultLimit: 10, // optional
  },
  // optional: off by default. `true`, or an object to tune storage
  chatHistory: { ttlSeconds: 60 * 60 * 24 * 30 },
});
```

<Accordion title="Dropping or overriding a contribution">
  Mount as a directory and override a slot by filename. This is how you drop a tool you don't want,
  for example to capture chat history without letting the model read it back:

  ```
  agent/extensions/agentkit/
    extension.ts                     # the mount: export default agentkit({ ... })
    tools/read_chat_history.ts       # your override for agentkit__read_chat_history
  ```

  ```ts
  // agent/extensions/agentkit/tools/read_chat_history.ts
  import { disableTool } from "eve/tools";

  export default disableTool();
  ```

  You can also re-define the memory tools, say to gate saves behind approval, by importing them from
  `@upstash/agentkit-eve-extension/tools` and spreading them into your own `defineTool`.
</Accordion>

<Note>
  `@upstash/agentkit-eve` below offers memory and RAG as standalone tool files too. Use those when you
  want to configure one tool at a time, and use the package alongside the extension for rate limiting
  and sandboxes.
</Note>

## The Eve package: rate limiting, sandboxes, and tool files

Everything in this part comes from `@upstash/agentkit-eve`, written as individual `agent/` files.
Rate limiting and the sandbox backend live here because an extension cannot contribute a channel or
a sandbox.

### How to add rate limiting to Vercel Eve

```bash
npm install @upstash/agentkit-eve @upstash/redis
```

`createRateLimitAuth` is a ready `AuthFn` that throttles inbound requests. Drop it into your channel's
[auth walk](https://eve.dev/docs/guides/auth-and-route-protection) ahead of your real authenticators.

```ts
// agent/channels/eve.ts
import { createRateLimitAuth, Ratelimit } from "@upstash/agentkit-eve";
import { localDev, vercelOidc } from "eve/channels/auth";
import { eveChannel } from "eve/channels/eve";

export default eveChannel({
  auth: [
    createRateLimitAuth({
      limiter: Ratelimit.slidingWindow(20, "1 m"),
      identifier: (req) => req.headers.get("x-forwarded-for") ?? "anonymous",
    }),
    localDev(),
    vercelOidc(),
  ],
});
```

<AccordionGroup>
  <Accordion title="Options and the required identifier">
    * **`limiter`** _(required)_ — e.g. `Ratelimit.slidingWindow(20, "1 m")` or `fixedWindow(...)`.
    * **`identifier`** _(required)_ — a string, or `(request) => string`. There's no implicit `"global"`:
      one shared bucket lets a single abusive caller exhaust the window for everyone, so derive it per
      request (an auth user id, an API key, or `x-forwarded-for` for per-IP).
    * `prefix` — base key prefix; keys are `<prefix>:<identifier>` (default `agentkit:rateLimit`).
    * `message` — 403 body when over the limit.
    * `redis` — defaults to `Redis.fromEnv()`.

    It's a gate: under the limit it returns `null` to fall through to the next `AuthFn`; over it throws
    a 403.
  </Accordion>
  <Accordion title="Why only POST requests are counted">
    Eve runs each turn as two authenticated requests: the message `POST` (which invokes the model) and a
    follow-up `GET …/stream` that opens the reply stream. The auth walk runs on both, so counting both
    would charge every turn twice. `createRateLimitAuth` counts only the `POST`s, so one turn costs one
    token: a `Ratelimit.slidingWindow(20, "1 m")` allows 20 turns per minute, not 10. The session-read
    `GET`s pass through unthrottled.
  </Accordion>
</AccordionGroup>

### How to add a sandbox to Vercel Eve

A drop-in replacement for Eve's `vercel()` backend, powered by
[Upstash Box](https://github.com/upstash/box). Swap the import and keep the rest of your
[sandbox file](https://eve.dev/docs/sandbox) the same.

```bash
npm install @upstash/box
```

```ts
// agent/sandbox.ts
import { defineSandbox } from "eve/sandbox";
import { upstash } from "@upstash/agentkit-eve/sandbox"; // was: eve/sandbox/vercel

export default defineSandbox({
  backend: upstash({ runtime: "node", size: "medium" }),
  revalidationKey: () => "repo-bootstrap-v1",
  async bootstrap({ use }) {
    const sandbox = await use({ networkPolicy: "allow-all" }); // open egress to install packages
    await sandbox.run({ command: "apt-get install -y jq" });
  },
  async onSession({ use }) {
    await use(); // inherits the secure deny-all default
  },
});
```

<AccordionGroup>
  <Accordion title="Config: Box's BoxConfig">
    `upstash(config)` takes the `@upstash/box` `BoxConfig` verbatim, meaning whatever you'd pass to
    `Box.create({...})`: `runtime`, `size`, `apiKey` (defaults to `UPSTASH_BOX_API_KEY`), `keepAlive`,
    `initCommand`, `env`, `skills`, `mcpServers`, `timeout`, and so on. It also takes an optional
    `redis` (defaults to `Redis.fromEnv()`). `networkPolicy` is **not** a config knob (see below).
    `@upstash/box` is an optional peer dependency, needed only when you import
    `@upstash/agentkit-eve/sandbox`.
  </Accordion>
  <Accordion title="Security: network egress is deny-all by default">
    The sandbox runs untrusted, model-generated code, so open egress would mean SSRF / data
    exfiltration / reaching your own infrastructure from inside the box. Open it per-session, in
    `bootstrap`'s `use(...)` or the session `use(...)`, and never as a config knob. Note that `env` passed
    to `upstash({ env })` is readable by code running in the box; don't pass secrets you wouldn't want
    it to see.
  </Accordion>
  <Accordion title="Brokering credentials (injecting headers)">
    Box network policies are plain domain/CIDR allow-lists. Eve's per-domain firewall rules (`transform`
    header injection, `forwardURL`) have no Box equivalent, so passing them in `use({ networkPolicy })`
    **throws** rather than silently sending the request unauthenticated:

    ```ts
    // ❌ throws — Box can't inject headers via a per-session policy
    export default defineSandbox({
      backend: upstash({ runtime: "node" }),
      async onSession({ use }) {
        await use({
          networkPolicy: {
            allow: { "api.example.com": [{ transform: [{ headers: { authorization: "Bearer …" } }] }] },
          },
        });
      },
    });
    ```

    Broker credentials with Box's `attachHeaders` instead (set at backend creation; a proxy on the box
    injects them), and open the domain with a plain allow-list:

    ```ts
    // ✅ headers injected at the firewall; the secret never enters the box
    export default defineSandbox({
      backend: upstash({
        runtime: "node",
        attachHeaders: { "api.example.com": { Authorization: "Bearer …" } },
      }),
      async onSession({ use }) {
        await use({ networkPolicy: { allow: ["api.example.com"] } });
      },
    });
    ```
  </Accordion>
  <Accordion title="Lifecycle: one box per conversation">
    **Reuse.** Eve re-opens a session several times per turn, and the backend reattaches to the same Box
    instead of creating a new one each time. Boxes default to Box's pause-based idle lifecycle
    (`keepAlive: false`): auto-paused when idle, resumed on reattach, reaped by Box. Pass
    `keepAlive: true` only for an always-running box you manage yourself.

    **Template registry.** Eve builds your template (seed files + `bootstrap`) at build/startup, but
    session creation runs per request in a different process, so the snapshot id is stored in a durable
    Redis registry (`redis`, defaulting to `Redis.fromEnv()`). Eve roots its tools at `/workspace` while
    a Box session lives at `/workspace/home`; the backend bridges the two automatically.
  </Accordion>
</AccordionGroup>

### How to cache tools in Vercel Eve

Like Eve's `defineTool`, but the `execute` result is memoized in Redis.

```ts
// agent/tools/get_weather.ts
import { z } from "zod";
import { defineCachedTool } from "@upstash/agentkit-eve";

export default defineCachedTool({
  description: "Get the current weather for a city.",
  inputSchema: z.object({ city: z.string() }),
  execute: async ({ city }) => fetchWeather(city),
  toolName: "get_weather",
  userId: (_, ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
});
```

<Accordion title="Options">
  * `description` / `inputSchema` / `execute` — the usual `defineTool` fields; `execute`'s result is memoized.
  * **`toolName`** _(required)_ — the tool segment of the cache key.
  * **`userId`** _(required)_ — a string, or `(input, ctx) => string`; scopes the cache per user.
  * `ttlSeconds` — per-result TTL (default: no expiry).
  * `redis` — defaults to `Redis.fromEnv()`.

  Keys are `agentkit:toolCache:<userId>:<toolName>:<hash>`.
</Accordion>

### Memory and RAG as individual tool files

The same two features the extension mounts, written as standalone `agent/tools/` files. Use these when
you want to configure each tool on its own.

Memory takes one file per tool:

```ts
// agent/tools/recall_memory.ts
import { defineMemoryRecallTool } from "@upstash/agentkit-eve";

export default defineMemoryRecallTool({
  userId: (_, ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
});
```

```ts
// agent/tools/save_memory.ts
import { defineMemorySaveTool } from "@upstash/agentkit-eve";

export default defineMemorySaveTool({
  userId: (_, ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
});
```

RAG is `defineSearchTools`, the counterpart to the
[AI SDK adapter's](/docs/redis/sdks/agentkit/ai-sdk#how-to-add-rag-with-the-ai-sdk) `createSearchTools`:

```ts
// agent/tools/search_books.ts
import { s } from "@upstash/redis";
import { defineSearchTools } from "@upstash/agentkit-eve";

export default defineSearchTools({
  schema: s.object({ title: s.string(), author: s.string().noTokenize(), year: s.number() }),
  indexName: "books",
}).search; // aggregate_books.ts → .aggregate, count_books.ts → .count
```

<Accordion title="Options and the one-file-per-tool rule">
  `defineMemoryRecallTool` / `defineMemorySaveTool` take a required `userId` (string or
  `(input, ctx) => string`), plus `topK`, `minScore`, and `redis`. `defineSearchTools` takes a required
  `schema`, plus `indexName`, `prefix`, `defaultLimit`, and `redis`.

  Each tool file must be self-contained, so call `defineSearchTools` in each one and export the member
  you want, repeating the same `schema` and `indexName` across `search_books.ts`, `aggregate_books.ts`,
  and `count_books.ts`. The index is created on first use, and every returned tool is already
  `defineTool`-branded.

  This package has no chat history. It comes from the extension, or from
  [`ChatHistory` in `@upstash/agentkit-sdk`](#how-to-add-searchable-chat-history-to-vercel-eve) if
  you're writing the code yourself.
</Accordion>

## Working with Eve's `agent/` files

Eve's runtime snapshots each tool/channel/hook file and resolves only **package** imports from it. It
does **not** include shared `agent/`-source modules such as an `agent/lib/redis.ts`. So inside `agent/`:

* Import only from packages, never from other `agent/` files.
* Lean on the defaults. `redis` falls back to `Redis.fromEnv()` in every helper, so you almost never pass it.
* Repeat config (schema, names) in each file instead of sharing a module.

Shared app code, like a seeder a page calls, belongs in your project `lib/` and is imported by the app,
not by `agent/` files. Extensions are exempt from all of this. The extension package ships as one
compiled unit, which is why its whole configuration fits in a single mount file.

## How to run the Vercel Eve example apps

Two complete `eve` apps live in the AgentKit repo:

* [`examples/eve-extension-demo`](https://github.com/upstash/agentkit/tree/main/examples/eve-extension-demo)
  is a minimal agent whose whole configuration is one extension mount, with memory, chat history, and
  book search turned on.
* [`examples/eve-demo`](https://github.com/upstash/agentkit/tree/main/examples/eve-demo) uses the
  file-by-file package (memory, search, cached tools, a rate-limit gate, and an Upstash Box sandbox),
  with a chat UI that renders tool calls inline.

<CardGroup cols={2}>
  <Card title="AgentKit on GitHub" icon="github" href="https://github.com/upstash/agentkit">
    Source, packages, and the full example apps.
  </Card>
  <Card title="Eve" icon="up-right-from-square" href="https://eve.dev">
    The Vercel agent framework this adapter targets.
  </Card>
</CardGroup>

# Upstash Redis MCP
Source: https://upstash.com/docs/redis/sdks/mcp

The Upstash Redis MCP server connects your agent directly to a Redis database. It is lightweight and ships with just two tools:

* **`redis_run_commands`**: run any Redis command (including the `SEARCH.*` family), as a pipeline or an atomic transaction.
* **`redis_search_docs`**: search the Upstash Redis docs live, so the agent can look up commands and features on demand.

Find the GitHub repository [here](https://github.com/upstash/redis-mcp).

<Note>
  To manage your whole Upstash account (create databases, read QStash and Workflow logs, drive Upstash Box), use the [Upstash MCP server](/docs/agent-resources/mcp) instead.
</Note>

# Installation

Grab your database's REST URL and token from the [Upstash Console](https://console.upstash.com) (open your Redis database and copy `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the **REST API** section), then add the server to your MCP client.

To connect over TCP instead, set `UPSTASH_REDIS_TCP_URL` to your `rediss://` connection string in place of the two REST variables.

<AccordionGroup>
  <Accordion title="Claude Code">
    ```sh
    claude mcp add upstash-redis \
      -e UPSTASH_REDIS_REST_URL=https://YOUR_DB.upstash.io \
      -e UPSTASH_REDIS_REST_TOKEN=YOUR_REST_TOKEN \
      -- npx -y @upstash/redis-mcp
    ```
  </Accordion>

  <Accordion title="Cursor">
    [![Add to Cursor]()](https://cursor.com/install-mcp?name=upstash-redis&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB1cHN0YXNoL3JlZGlzLW1jcCJdfQ==)

    ```json
    {
      "mcpServers": {
        "upstash-redis": {
          "command": "npx",
          "args": ["-y", "@upstash/redis-mcp"],
          "env": {
            "UPSTASH_REDIS_REST_URL": "https://YOUR_DB.upstash.io",
            "UPSTASH_REDIS_REST_TOKEN": "YOUR_REST_TOKEN"
          }
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="VS Code">
    ```json
    {
      "servers": {
        "upstash-redis": {
          "type": "stdio",
          "command": "npx",
          "args": ["-y", "@upstash/redis-mcp"],
          "env": {
            "UPSTASH_REDIS_REST_URL": "https://YOUR_DB.upstash.io",
            "UPSTASH_REDIS_REST_TOKEN": "YOUR_REST_TOKEN"
          }
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="OpenAI Codex">
    ```toml
    [mcp_servers.upstash-redis]
    command = "npx"
    args = ["-y", "@upstash/redis-mcp"]
    env = { UPSTASH_REDIS_REST_URL = "https://YOUR_DB.upstash.io", UPSTASH_REDIS_REST_TOKEN = "YOUR_REST_TOKEN" }
    ```
  </Accordion>

  <Accordion title="OpenCode">
    ```json
    {
      "mcp": {
        "upstash-redis": {
          "type": "local",
          "command": ["npx", "-y", "@upstash/redis-mcp"],
          "environment": {
            "UPSTASH_REDIS_REST_URL": "https://YOUR_DB.upstash.io",
            "UPSTASH_REDIS_REST_TOKEN": "YOUR_REST_TOKEN"
          }
        }
      }
    }
    ```
  </Accordion>
</AccordionGroup>

Any MCP-compatible client works. If yours isn't listed, add a stdio server that runs `npx -y @upstash/redis-mcp` with the two env vars above.

- [ECHO](https://upstash.com/docs/redis/sdks/py/commands/auth/echo.md)
- [PING](https://upstash.com/docs/redis/sdks/py/commands/auth/ping.md): Send a ping to the server and get a response if the server is alive.
- [BITCOUNT](https://upstash.com/docs/redis/sdks/py/commands/bitmap/bitcount.md): Count the number of set bits.
- [BITFIELD](https://upstash.com/docs/redis/sdks/py/commands/bitmap/bitfield.md): Sets or gets parts of a bitfield
- [BITOP](https://upstash.com/docs/redis/sdks/py/commands/bitmap/bitop.md): Perform bitwise operations between strings.
- [BITPOS](https://upstash.com/docs/redis/sdks/py/commands/bitmap/bitpos.md): Find the position of the first set or clear bit (bit with a value of 1 or 0) in a Redis string key.
- [GETBIT](https://upstash.com/docs/redis/sdks/py/commands/bitmap/getbit.md): Retrieve a single bit.
- [SETBIT](https://upstash.com/docs/redis/sdks/py/commands/bitmap/setbit.md): Set a single bit in a string.
- [CLIENT SETINFO](https://upstash.com/docs/redis/sdks/py/commands/connection/client_setinfo.md): Set client library name and version information.
- [DEL](https://upstash.com/docs/redis/sdks/py/commands/generic/del.md): Removes the specified keys. A key is ignored if it does not exist.
- [EXISTS](https://upstash.com/docs/redis/sdks/py/commands/generic/exists.md): Check if a key exists.
- [EXPIRE](https://upstash.com/docs/redis/sdks/py/commands/generic/expire.md): Sets a timeout on key. The key will automatically be deleted.
- [EXPIREAT](https://upstash.com/docs/redis/sdks/py/commands/generic/expireat.md): Sets a timeout on key. The key will automatically be deleted.
- [KEYS](https://upstash.com/docs/redis/sdks/py/commands/generic/keys.md): Returns all keys matching pattern.
- [PERSIST](https://upstash.com/docs/redis/sdks/py/commands/generic/persist.md): Remove any timeout set on the key.
- [PEXPIRE](https://upstash.com/docs/redis/sdks/py/commands/generic/pexpire.md): Sets a timeout on key. After the timeout has expired, the key will automatically be deleted.
- [PEXPIREAT](https://upstash.com/docs/redis/sdks/py/commands/generic/pexpireat.md): Sets a timeout on key. After the timeout has expired, the key will automatically be deleted.
- [PTTL](https://upstash.com/docs/redis/sdks/py/commands/generic/pttl.md): Return the expiration in milliseconds of a key.
- [RANDOMKEY](https://upstash.com/docs/redis/sdks/py/commands/generic/randomkey.md): Returns a random key from database
- [RENAME](https://upstash.com/docs/redis/sdks/py/commands/generic/rename.md): Rename a key
- [RENAMENX](https://upstash.com/docs/redis/sdks/py/commands/generic/renamenx.md): Rename a key if it does not already exist.
- [SCAN](https://upstash.com/docs/redis/sdks/py/commands/generic/scan.md): Scan the database for keys.
- [TOUCH](https://upstash.com/docs/redis/sdks/py/commands/generic/touch.md): Alters the last access time of one or more keys
- [TTL](https://upstash.com/docs/redis/sdks/py/commands/generic/ttl.md): Return the expiration in seconds of a key.
- [TYPE](https://upstash.com/docs/redis/sdks/py/commands/generic/type.md): Get the type of a key.
- [UNLINK](https://upstash.com/docs/redis/sdks/py/commands/generic/unlink.md): Removes the specified keys. A key is ignored if it does not exist.
- [HDEL](https://upstash.com/docs/redis/sdks/py/commands/hash/hdel.md): Deletes one or more hash fields.
- [HEXISTS](https://upstash.com/docs/redis/sdks/py/commands/hash/hexists.md): Checks if a field exists in a hash.
- [HEXPIRE](https://upstash.com/docs/redis/sdks/py/commands/hash/hexpire.md): Set a timeout on a hash field in seconds.
- [HEXPIREAT](https://upstash.com/docs/redis/sdks/py/commands/hash/hexpireat.md): Sets an expiration time for field(s) in a hash in seconds since the Unix epoch.
- [HEXPIRETIME](https://upstash.com/docs/redis/sdks/py/commands/hash/hexpiretime.md): Retrieves the expiration time of field(s) in a hash in seconds.
- [HGET](https://upstash.com/docs/redis/sdks/py/commands/hash/hget.md): Retrieves the value of a hash field.
- [HGETALL](https://upstash.com/docs/redis/sdks/py/commands/hash/hgetall.md): Retrieves all fields from a hash.
- [HGETDEL](https://upstash.com/docs/redis/sdks/py/commands/hash/hgetdel.md): Get and delete hash fields atomically.
- [HGETEX](https://upstash.com/docs/redis/sdks/py/commands/hash/hgetex.md): Get hash fields with expiration support.
- [HINCRBY](https://upstash.com/docs/redis/sdks/py/commands/hash/hincrby.md): Increments the value of a hash field by a given amount
- [HINCRBYFLOAT](https://upstash.com/docs/redis/sdks/py/commands/hash/hincrbyfloat.md): Increments the value of a hash field by a given float value.
- [HKEYS](https://upstash.com/docs/redis/sdks/py/commands/hash/hkeys.md): Return all field names in the hash stored at key.
- [HLEN](https://upstash.com/docs/redis/sdks/py/commands/hash/hlen.md): Returns the number of fields contained in the hash stored at key.
- [HMGET](https://upstash.com/docs/redis/sdks/py/commands/hash/hmget.md): Return the requested fields and their values.
- [HMSET](https://upstash.com/docs/redis/sdks/py/commands/hash/hmset.md): Write multiple fields to a hash.
- [HPERSIST](https://upstash.com/docs/redis/sdks/py/commands/hash/hpersist.md): Remove the expiration from one or more hash fields.
- [HPEXPIRE](https://upstash.com/docs/redis/sdks/py/commands/hash/hpexpire.md): Set a timeout on a hash field in milliseconds.
- [HPEXPIREAT](https://upstash.com/docs/redis/sdks/py/commands/hash/hpexpireat.md): Sets an expiration time for field(s) in a hash in milliseconds since the Unix epoch.
- [HPEXPIRETIME](https://upstash.com/docs/redis/sdks/py/commands/hash/hpexpiretime.md): Retrieves the expiration time of a field in a hash in milliseconds.
- [HPTTL](https://upstash.com/docs/redis/sdks/py/commands/hash/hpttl.md): Retrieves the remaining time-to-live (TTL) for field(s) in a hash in milliseconds.
- [HRANDFIELD](https://upstash.com/docs/redis/sdks/py/commands/hash/hrandfield.md): Return a random field from a hash
- [HSCAN](https://upstash.com/docs/redis/sdks/py/commands/hash/hscan.md): Scan a hash for fields.
- [HSET](https://upstash.com/docs/redis/sdks/py/commands/hash/hset.md): Write one or more fields to a hash.
- [HSETEX](https://upstash.com/docs/redis/sdks/py/commands/hash/hsetex.md): Set hash fields with expiration support.
- [HSETNX](https://upstash.com/docs/redis/sdks/py/commands/hash/hsetnx.md): Write a field to a hash but only if the field does not exist.
- [HSTRLEN](https://upstash.com/docs/redis/sdks/py/commands/hash/hstrlen.md): Returns the string length of a value in a hash.
- [HTTL](https://upstash.com/docs/redis/sdks/py/commands/hash/httl.md): Retrieves the remaining time-to-live (TTL) for field(s) in a hash in seconds.
- [HVALS](https://upstash.com/docs/redis/sdks/py/commands/hash/hvals.md): Returns all values in the hash stored at key.
- [JSON.ARRAPPEND](https://upstash.com/docs/redis/sdks/py/commands/json/arrappend.md): Append values to the array at path in the JSON document at key.
- [JSON.ARRINDEX](https://upstash.com/docs/redis/sdks/py/commands/json/arrindex.md): Search for the first occurrence of a JSON value in an array.
- [JSON.ARRINSERT](https://upstash.com/docs/redis/sdks/py/commands/json/arrinsert.md): Insert the json values into the array at path before the index (shifts to the right).
- [JSON.ARRLEN](https://upstash.com/docs/redis/sdks/py/commands/json/arrlen.md): Report the length of the JSON array at `path` in `key`.
- [JSON.ARRPOP](https://upstash.com/docs/redis/sdks/py/commands/json/arrpop.md): Remove and return an element from the index in the array. By default the last element from an array is popped.
- [JSON.ARRTRIM](https://upstash.com/docs/redis/sdks/py/commands/json/arrtrim.md): Trim an array so that it contains only the specified inclusive range of elements.
- [JSON.CLEAR](https://upstash.com/docs/redis/sdks/py/commands/json/clear.md): Clear container values (arrays/objects) and set numeric values to 0.
- [JSON.DEL](https://upstash.com/docs/redis/sdks/py/commands/json/del.md): Delete a key from a JSON document.
- [JSON.FORGET](https://upstash.com/docs/redis/sdks/py/commands/json/forget.md): Delete a key from a JSON document.
- [JSON.GET](https://upstash.com/docs/redis/sdks/py/commands/json/get.md): Get a single value from a JSON document.
- [JSON.MERGE](https://upstash.com/docs/redis/sdks/py/commands/json/merge.md): Merges the JSON value at path in key with the provided value.
- [JSON.MGET](https://upstash.com/docs/redis/sdks/py/commands/json/mget.md): Get the same path from multiple JSON documents.
- [JSON.MSET](https://upstash.com/docs/redis/sdks/py/commands/json/mset.md): Sets multiple JSON values at multiple paths in multiple keys.
- [JSON.NUMINCRBY](https://upstash.com/docs/redis/sdks/py/commands/json/numincrby.md): Increment the number value stored at `path` by number.
- [JSON.NUMMULTBY](https://upstash.com/docs/redis/sdks/py/commands/json/nummultby.md): Multiply the number value stored at `path` by number.
- [JSON.OBJKEYS](https://upstash.com/docs/redis/sdks/py/commands/json/objkeys.md): Return the keys in the object that`s referenced by path.
- [JSON.OBJLEN](https://upstash.com/docs/redis/sdks/py/commands/json/objlen.md): Report the number of keys in the JSON object at `path` in `key`.
- [JSON.RESP](https://upstash.com/docs/redis/sdks/py/commands/json/resp.md): Return the value at the path in Redis serialization protocol format.
- [JSON.SET](https://upstash.com/docs/redis/sdks/py/commands/json/set.md): Set the JSON value at path in key.
- [JSON.STRAPPEND](https://upstash.com/docs/redis/sdks/py/commands/json/strappend.md): Append the json-string values to the string at path.
- [JSON.STRLEN](https://upstash.com/docs/redis/sdks/py/commands/json/strlen.md): Report the length of the JSON String at path in key
- [JSON.TOGGLE](https://upstash.com/docs/redis/sdks/py/commands/json/toggle.md): Toggle a boolean value stored at `path`.
- [JSON.TYPE](https://upstash.com/docs/redis/sdks/py/commands/json/type.md): Report the type of JSON value at `path`.
- [LINDEX](https://upstash.com/docs/redis/sdks/py/commands/list/lindex.md): Returns the element at index index in the list stored at key.
- [LINSERT](https://upstash.com/docs/redis/sdks/py/commands/list/linsert.md): Insert an element before or after another element in a list
- [LLEN](https://upstash.com/docs/redis/sdks/py/commands/list/llen.md): Returns the length of the list stored at key.
- [LMOVE](https://upstash.com/docs/redis/sdks/py/commands/list/lmove.md): Move an element from one list to another.
- [LPOP](https://upstash.com/docs/redis/sdks/py/commands/list/lpop.md): Remove and return the first element(s) of a list
- [LPOS](https://upstash.com/docs/redis/sdks/py/commands/list/lpos.md): Returns the index of matching elements inside a list.
- [LPUSH](https://upstash.com/docs/redis/sdks/py/commands/list/lpush.md): Push an element at the head of the list.
- [LPUSHX](https://upstash.com/docs/redis/sdks/py/commands/list/lpushx.md): Push an element at the head of the list only if the list exists.
- [LRANGE](https://upstash.com/docs/redis/sdks/py/commands/list/lrange.md): Returns the specified elements of the list stored at key.
- [LREM](https://upstash.com/docs/redis/sdks/py/commands/list/lrem.md): Remove the first `count` occurrences of an element from a list.
- [LSET](https://upstash.com/docs/redis/sdks/py/commands/list/lset.md): Set a value at a specific index.
- [LTRIM](https://upstash.com/docs/redis/sdks/py/commands/list/ltrim.md): Trim a list to the specified range
- [RPOP](https://upstash.com/docs/redis/sdks/py/commands/list/rpop.md): Remove and return the last element(s) of a list
- [RPUSH](https://upstash.com/docs/redis/sdks/py/commands/list/rpush.md): Push an element at the end of the list.
- [RPUSHX](https://upstash.com/docs/redis/sdks/py/commands/list/rpushx.md): Push an element at the end of the list only if the list exists.
- [Overview](https://upstash.com/docs/redis/sdks/py/commands/overview.md): Available Commands in upstash-redis
- [PUBLISH](https://upstash.com/docs/redis/sdks/py/commands/pubsub/publish.md): Publish a message to a channel
- [EVAL](https://upstash.com/docs/redis/sdks/py/commands/scripts/eval.md): Evaluate a Lua script server side.
- [EVAL_RO](https://upstash.com/docs/redis/sdks/py/commands/scripts/eval_ro.md): Evaluate a read-only Lua script server side.
- [EVALSHA](https://upstash.com/docs/redis/sdks/py/commands/scripts/evalsha.md): Evaluate a cached Lua script server side.
- [EVALSHA_RO](https://upstash.com/docs/redis/sdks/py/commands/scripts/evalsha_ro.md): Evaluate a cached read-only Lua script server side.
- [SCRIPT EXISTS](https://upstash.com/docs/redis/sdks/py/commands/scripts/script_exists.md): Check if scripts exist in the script cache.
- [SCRIPT FLUSH](https://upstash.com/docs/redis/sdks/py/commands/scripts/script_flush.md): Removes all scripts from the script cache.
- [SCRIPT LOAD](https://upstash.com/docs/redis/sdks/py/commands/scripts/script_load.md): Load the specified Lua script into the script cache.
- [DBSIZE](https://upstash.com/docs/redis/sdks/py/commands/server/dbsize.md): Count the number of keys in the database.
- [FLUSHALL](https://upstash.com/docs/redis/sdks/py/commands/server/flushall.md)
- [FLUSHDB](https://upstash.com/docs/redis/sdks/py/commands/server/flushdb.md)
- [SADD](https://upstash.com/docs/redis/sdks/py/commands/set/sadd.md): Adds one or more members to a set.
- [SCARD](https://upstash.com/docs/redis/sdks/py/commands/set/scard.md): Return how many members are in a set
- [SDIFF](https://upstash.com/docs/redis/sdks/py/commands/set/sdiff.md): Return the difference between sets
- [SDIFFSTORE](https://upstash.com/docs/redis/sdks/py/commands/set/sdiffstore.md): Write the difference between sets to a new set
- [SINTER](https://upstash.com/docs/redis/sdks/py/commands/set/sinter.md): Return the intersection between sets
- [SINTER](https://upstash.com/docs/redis/sdks/py/commands/set/sinterstore.md): Return the intersection between sets and store the resulting set in a key
- [SISMEMBER](https://upstash.com/docs/redis/sdks/py/commands/set/sismember.md): Check if a member exists in a set
- [SMEMBERS](https://upstash.com/docs/redis/sdks/py/commands/set/smembers.md): Return all the members of a set
- [SMISMEMBER](https://upstash.com/docs/redis/sdks/py/commands/set/smismember.md): Check if multiple members exist in a set
- [SMOVE](https://upstash.com/docs/redis/sdks/py/commands/set/smove.md): Move a member from one set to another
- [SPOP](https://upstash.com/docs/redis/sdks/py/commands/set/spop.md): Removes and returns one or more random members from a set.
- [SRANDMEMBER](https://upstash.com/docs/redis/sdks/py/commands/set/srandmember.md): Returns one or more random members from a set.
- [SREM](https://upstash.com/docs/redis/sdks/py/commands/set/srem.md): Remove one or more members from a set
- [SSCAN](https://upstash.com/docs/redis/sdks/py/commands/set/sscan.md): Scan a set
- [SUNION](https://upstash.com/docs/redis/sdks/py/commands/set/sunion.md): Return the union between sets
- [SUNIONSTORE](https://upstash.com/docs/redis/sdks/py/commands/set/sunionstore.md): Return the union between sets and store the resulting set in a key
- [XACK](https://upstash.com/docs/redis/sdks/py/commands/stream/xack.md): Removes one or multiple messages from the pending entries list of a stream consumer group.
- [XACKDEL](https://upstash.com/docs/redis/sdks/py/commands/stream/xackdel.md): Acknowledge and delete stream entries atomically.
- [XADD](https://upstash.com/docs/redis/sdks/py/commands/stream/xadd.md): Appends one or more new entries to a stream.
- [XAUTOCLAIM](https://upstash.com/docs/redis/sdks/py/commands/stream/xautoclaim.md): Changes the ownership of pending messages from one consumer to another in a stream consumer group automatically.
- [XCLAIM](https://upstash.com/docs/redis/sdks/py/commands/stream/xclaim.md): Changes the ownership of pending messages from one consumer to another in a stream consumer group.
- [XDEL](https://upstash.com/docs/redis/sdks/py/commands/stream/xdel.md): Removes the specified entries from a stream, and returns the number of entries deleted.
- [XDELEX](https://upstash.com/docs/redis/sdks/py/commands/stream/xdelex.md): Extended delete for streams with reference control.
- [XGROUP CREATE](https://upstash.com/docs/redis/sdks/py/commands/stream/xgroup_create.md): Create a new consumer group for a Redis stream.
- [XGROUP CREATECONSUMER](https://upstash.com/docs/redis/sdks/py/commands/stream/xgroup_createconsumer.md): Create a new consumer in an existing consumer group.
- [XGROUP DELCONSUMER](https://upstash.com/docs/redis/sdks/py/commands/stream/xgroup_delconsumer.md): Delete a consumer from a consumer group.
- [XGROUP DESTROY](https://upstash.com/docs/redis/sdks/py/commands/stream/xgroup_destroy.md): Delete an entire consumer group.
- [XGROUP SETID](https://upstash.com/docs/redis/sdks/py/commands/stream/xgroup_setid.md): Set the last delivered ID for a consumer group.
- [XINFO CONSUMERS](https://upstash.com/docs/redis/sdks/py/commands/stream/xinfo_consumers.md): List all consumers in a consumer group.
- [XINFO GROUPS](https://upstash.com/docs/redis/sdks/py/commands/stream/xinfo_groups.md): List all consumer groups for a stream.
- [XLEN](https://upstash.com/docs/redis/sdks/py/commands/stream/xlen.md): Returns the number of entries inside a stream.
- [XPENDING](https://upstash.com/docs/redis/sdks/py/commands/stream/xpending.md): Returns information about pending messages in a stream consumer group.
- [XRANGE](https://upstash.com/docs/redis/sdks/py/commands/stream/xrange.md): Returns stream entries matching a given range of IDs.
- [XREAD](https://upstash.com/docs/redis/sdks/py/commands/stream/xread.md): Reads data from one or multiple streams, starting from the specified IDs.
- [XREADGROUP](https://upstash.com/docs/redis/sdks/py/commands/stream/xreadgroup.md): Reads data from a stream as part of a consumer group.
- [XREVRANGE](https://upstash.com/docs/redis/sdks/py/commands/stream/xrevrange.md): Returns stream entries matching a given range of IDs in reverse order.
- [XTRIM](https://upstash.com/docs/redis/sdks/py/commands/stream/xtrim.md): Trims the stream by removing entries to keep it at a reasonable size.
- [APPEND](https://upstash.com/docs/redis/sdks/py/commands/string/append.md): Append a value to a string stored at key.
- [DECR](https://upstash.com/docs/redis/sdks/py/commands/string/decr.md): Decrement the integer value of a key by one
- [DECRBY](https://upstash.com/docs/redis/sdks/py/commands/string/decrby.md): Decrement the integer value of a key by a given number.
- [GET](https://upstash.com/docs/redis/sdks/py/commands/string/get.md): Return the value of the specified key or `None` if the key doesn't exist.
- [GETDEL](https://upstash.com/docs/redis/sdks/py/commands/string/getdel.md): Return the value of the specified key and delete the key.
- [GETRANGE](https://upstash.com/docs/redis/sdks/py/commands/string/getrange.md): Return a substring of value at the specified key.
- [GETSET](https://upstash.com/docs/redis/sdks/py/commands/string/getset.md): Return the value of the specified key and replace it with a new value.
- [INCR](https://upstash.com/docs/redis/sdks/py/commands/string/incr.md): Increment the integer value of a key by one
- [INCRBY](https://upstash.com/docs/redis/sdks/py/commands/string/incrby.md): Increment the integer value of a key by a given number.
- [INCRBYFLOAT](https://upstash.com/docs/redis/sdks/py/commands/string/incrbyfloat.md): Increment the float value of a key by a given number.
- [MGET](https://upstash.com/docs/redis/sdks/py/commands/string/mget.md): Load multiple keys from Redis in one go.
- [MSET](https://upstash.com/docs/redis/sdks/py/commands/string/mset.md): Set multiple keys in one go.
- [MSETNX](https://upstash.com/docs/redis/sdks/py/commands/string/msetnx.md): Set multiple keys in one go unless they exist already.
- [SET](https://upstash.com/docs/redis/sdks/py/commands/string/set.md): Set a key to hold a string value.
- [SETRANGE](https://upstash.com/docs/redis/sdks/py/commands/string/setrange.md): Writes the value of key at offset.
- [STRLEN](https://upstash.com/docs/redis/sdks/py/commands/string/strlen.md): Return the length of a string stored at a key.
- [ZADD](https://upstash.com/docs/redis/sdks/py/commands/zset/zadd.md): Add a member to a sorted set, or update its score if it already exists.
- [ZCARD](https://upstash.com/docs/redis/sdks/py/commands/zset/zcard.md): Returns the number of elements in the sorted set stored at key.
- [ZCOUNT](https://upstash.com/docs/redis/sdks/py/commands/zset/zcount.md): Returns the number of elements in the sorted set stored at key filterd by score.
- [ZDIFF](https://upstash.com/docs/redis/sdks/py/commands/zset/zdiff.md): Returns the difference between sets.
- [ZDIFFSTORE](https://upstash.com/docs/redis/sdks/py/commands/zset/zdiffstore.md): Writes the difference between sets to a new key.
- [ZINCRBY](https://upstash.com/docs/redis/sdks/py/commands/zset/zincrby.md): Increment the score of a member.
- [ZINTER](https://upstash.com/docs/redis/sdks/py/commands/zset/zinter.md): Returns the intersection between sets.
- [ZINTERSTORE](https://upstash.com/docs/redis/sdks/py/commands/zset/zinterstore.md): Calculates the intersection of sets and stores the result in a key
- [ZLEXCOUNT](https://upstash.com/docs/redis/sdks/py/commands/zset/zlexcount.md): Returns the number of elements in the sorted set stored at key filterd by lex.
- [ZMSCORE](https://upstash.com/docs/redis/sdks/py/commands/zset/zmscore.md): Returns the scores of multiple members.
- [ZPOPMAX](https://upstash.com/docs/redis/sdks/py/commands/zset/zpopmax.md): Removes and returns up to count members with the highest scores in the sorted set stored at key.
- [ZPOPMIN](https://upstash.com/docs/redis/sdks/py/commands/zset/zpopmin.md): Removes and returns up to count members with the lowest scores in the sorted set stored at key.
- [ZRANDMEMBER](https://upstash.com/docs/redis/sdks/py/commands/zset/zrandmember.md): Returns one or more random members from a sorted set, optionally with their scores.
- [ZRANGE](https://upstash.com/docs/redis/sdks/py/commands/zset/zrange.md): Returns the specified range of elements in the sorted set stored at key.
- [ZRANK](https://upstash.com/docs/redis/sdks/py/commands/zset/zrank.md): Returns the rank of a member
- [ZREM](https://upstash.com/docs/redis/sdks/py/commands/zset/zrem.md): Remove one or more members from a sorted set
- [ZREMRANGEBYLEX](https://upstash.com/docs/redis/sdks/py/commands/zset/zremrangebylex.md): Remove all members in a sorted set between the given lexicographical range.
- [ZREMRANGEBYRANK](https://upstash.com/docs/redis/sdks/py/commands/zset/zremrangebyrank.md): Remove all members in a sorted set between the given ranks.
- [ZREMRANGEBYSCORE](https://upstash.com/docs/redis/sdks/py/commands/zset/zremrangebyscore.md): Remove all members in a sorted set between the given scores.
- [ZREVRANK](https://upstash.com/docs/redis/sdks/py/commands/zset/zrevrank.md): Returns the rank of a member in a sorted set, with scores ordered from high to low.
- [ZSCAN](https://upstash.com/docs/redis/sdks/py/commands/zset/zscan.md): Scan a sorted set
- [ZSCORE](https://upstash.com/docs/redis/sdks/py/commands/zset/zscore.md): Returns the scores of a member.
- [ZINTER](https://upstash.com/docs/redis/sdks/py/commands/zset/zunion.md): Returns the intersection between sets.
- [ZUNIONSTORE](https://upstash.com/docs/redis/sdks/py/commands/zset/zunionstore.md): Writes the union between sets to a new key.

# Features
Source: https://upstash.com/docs/redis/sdks/py/features

### BITFIELD and BITFIELD_RO

One particular case is represented by these two chained commands, which are
available as functions that return an instance of the `BITFIELD` and,
respectively, `BITFIELD_RO` classes. Use the `execute` function to run the
commands.

```python
redis.bitfield("test_key") \
  .incrby(encoding="i8", offset=100, increment=100) \
  .overflow("SAT") \
  .incrby(encoding="i8", offset=100, increment=100) \
  .execute()

redis.bitfield_ro("test_key_2") \
  .get(encoding="u8", offset=0) \
  .get(encoding="u8", offset="#1") \
  .execute()
```

### Custom commands

If you want to run a command that hasn't been implemented, you can use the
`execute` function of your client instance and pass the command as a `list`.

```python
redis.execute(["XLEN", "test_stream"])
```

# Encoding

Although Redis can store invalid JSON data, there might be problems with the
deserialization. To avoid this, the Upstash REST proxy is capable of encoding
the data as base64 on the server and then sending it to the client to be
decoded.

For very large data, this can add a few milliseconds in latency. So, if you're
sure that your data is valid JSON, you can set `rest_encoding` to `None`.

# Retry mechanism

upstash-redis has a fallback mechanism in case of network or API issues. By
default, if a request fails it'll retry once, 3 seconds after the error. If you
want to customize that, set `rest_retries` and `rest_retry_interval` (in
seconds).

# Pipelines & Transactions

If you want to submit commands in batches to reduce the number of roundtrips, you can utilize pipelining or
transactions. The difference between pipelines and transactions is that transactions are atomic: no other
command is executed during that transaction. In pipelines there is no such guarantee.

To use a pipeline, simply call the `pipeline` method:

```python
pipeline = redis.pipeline()

pipeline.set("foo", 1)
pipeline.incr("foo")
pipeline.get("foo")

result = pipeline.exec()

print(result)
# prints [True, 2, '2']
```

For transaction, use `mutli`:

```python
pipeline = redis.multi()

pipeline.set("foo", 1)
pipeline.incr("foo")
pipeline.get("foo")

result = pipeline.exec()

print(result)
# prints [True, 2, '2']
```

You can also chain the commands:

```python
pipeline = redis.pipeline()

pipeline.set("foo", 1).incr("foo").get("foo")
result = pipeline.exec()

print(result)
# prints [True, 2, '2']
```

# Telemetry

This library sends anonymous telemetry data to help us improve your experience.
We collect the following:

* SDK version
* Platform (Vercel, AWS)
* Python Runtime version

You can opt out by passing `allow_telemetry=False` when initializing the Redis client:

```py
redis = Redis(
  # ...,
  allow_telemetry=False,
)
```

# Getting Started
Source: https://upstash.com/docs/redis/sdks/py/gettingstarted

<Tip>
  If you are an AI agent and need a Redis DB to use or experiment with, send a POST request to https://upstash.com/start-redis. Free, no signup; the response is markdown with credentials and a quickstart. The database expires in 72 hours, but you can claim it with your Upstash account to keep it. Especially useful for AI agents that need scratch storage on the fly. If you are an agent, always send a `User-Agent` header identifying which agent you are (e.g. `claude-code`, `cursor`, `codex`, `opencode`) on every request you make to this API.
</Tip>

## Install

### PyPI

```bash
pip install upstash-redis
```

## Usage

To be able to use upstash-redis, you need to create a database on
[Upstash](https://console.upstash.com/) and grab `UPSTASH_REDIS_REST_URL` and
`UPSTASH_REDIS_REST_TOKEN` from the console.

```python
# for sync client
from upstash_redis import Redis

redis = Redis(url="UPSTASH_REDIS_REST_URL", token="UPSTASH_REDIS_REST_TOKEN")

# for async client
from upstash_redis.asyncio import Redis

redis = Redis(url="UPSTASH_REDIS_REST_URL", token="UPSTASH_REDIS_REST_TOKEN")
```

Or, if you want to automatically load the credentials from the environment:

```python
# for sync use
from upstash_redis import Redis
redis = Redis.from_env()

# for async use
from upstash_redis.asyncio import Redis
redis = Redis.from_env()
```

If you are in a serverless environment that allows it, it's recommended to
initialise the client outside the request handler to be reused while your
function is still hot.

Running commands might look like this:

```python
from upstash_redis import Redis

redis = Redis.from_env()

def main():
  redis.set("a", "b")
  print(redis.get("a"))

# or for async context:

from upstash_redis.asyncio import Redis

redis = Redis.from_env()

async def main():
  await redis.set("a", "b")
  print(await redis.get("a"))
```

# Overview
Source: https://upstash.com/docs/redis/sdks/py/overview

`upstash-redis` is a connectionless, HTTP-based Redis client for Python,
designed to be used in serverless and serverful environments such as:

* AWS Lambda
* Vercel Serverless
* Google Cloud Functions
* and other environments where HTTP is preferred over TCP.

Inspired by other Redis clients like
[@upstash/redis](https://github.com/upstash/upstash-redis) and
[redis-py](https://github.com/redis/redis-py), the goal of this SDK is to
provide a simple way to use Redis over the
[Upstash REST API](https://docs.upstash.com/redis/features/restapi).

The SDK is currently compatible with Python 3.8 and above.

You can find the Github Repository [here](https://github.com/upstash/redis-python).

# Ratelimiting Algorithms
Source: https://upstash.com/docs/redis/sdks/ratelimit-py/algorithms

## Fixed Window

This algorithm divides time into fixed durations/windows. For example each
window is 10 seconds long. When a new request comes in, the current time is used
to determine the window and a counter is increased. If the counter is larger
than the set limit, the request is rejected.

<Tip>
  In fixed & sliding window algorithms, the reset time is based on fixed time boundaries (which depend on the period), not on when the first request was made. So two requests made right before the window ends still count toward the current window, and limits reset at the start of the next window.
</Tip>

### Pros

* Very cheap in terms of data size and computation
* Newer requests are not starved due to a high burst in the past

### Cons

* Can cause high bursts at the window boundaries to leak through
* Causes request stampedes if many users are trying to access your server,
  whenever a new window begins

### Usage

```python
from upstash_ratelimit import Ratelimit, FixedWindow
from upstash_redis import Redis

ratelimit = Ratelimit(
    redis=Redis.from_env(),
    limiter=FixedWindow(max_requests=10, window=10),
)
```

## Sliding Window

Builds on top of fixed window but instead of a fixed window, we use a rolling
window. Take this example: We have a rate limit of 10 requests per 1 minute. We
divide time into 1 minute slices, just like in the fixed window algorithm.
Window 1 will be from 00:00:00 to 00:01:00 (HH:MM:SS). Let's assume it is
currently 00:01:15 and we have received 4 requests in the first window and 5
requests so far in the current window. The approximation to determine if the
request should pass works like this:

```python
limit = 10

# 4 request from the old window, weighted + requests in current window
rate = 4 * ((60 - 15) / 60) + 5 = 8

return rate < limit # True means we should allow the request
```

### Pros

* Solves the issue near boundary from fixed window.

### Cons

* More expensive in terms of storage and computation
* It's only an approximation because it assumes a uniform request flow in the
  previous window

### Usage

```python
from upstash_ratelimit import Ratelimit, SlidingWindow
from upstash_redis import Redis

ratelimit = Ratelimit(
    redis=Redis.from_env(),
    limiter=SlidingWindow(max_requests=10, window=10),
)
```

<Tip>
  `reset` field in the [`limit`](/docs/redis/sdks/ratelimit-py/gettingstarted) method of sliding window does not
  provide an exact reset time. Instead, the reset time is the start time of
  the next window.
</Tip>

## Token Bucket

Consider a bucket filled with maximum number of tokens that refills constantly
at a rate per interval. Every request will remove one token from the bucket and
if there is no token to take, the request is rejected.

### Pros

* Bursts of requests are smoothed out and you can process them at a constant
  rate.
* Allows setting a higher initial burst limit by setting maximum number of
  tokens higher than the refill rate

### Cons

* Expensive in terms of computation

### Usage

```python
from upstash_ratelimit import Ratelimit, TokenBucket
from upstash_redis import Redis

ratelimit = Ratelimit(
    redis=Redis.from_env(),
    limiter=TokenBucket(max_tokens=10, refill_rate=5, interval=10),
)
```

# Features
Source: https://upstash.com/docs/redis/sdks/ratelimit-py/features

## Block until ready

You also have the option to try and wait for a request to pass in the given
timeout.

It is very similar to the `limit` method and takes an identifier and returns the
same response. However if the current limit has already been exceeded, it will
automatically wait until the next window starts and will try again. Setting the
timeout parameter (in seconds) will cause the method to block a finite amount of
time.

```python
from upstash_ratelimit import Ratelimit, SlidingWindow
from upstash_redis import Redis

# Create a new ratelimiter, that allows 10 requests per 10 seconds
ratelimit = Ratelimit(
    redis=Redis.from_env(),
    limiter=SlidingWindow(max_requests=10, window=10),
)

response = ratelimit.block_until_ready("id", timeout=30)

if not response.allowed:
    print("Unable to process, even after 30 seconds")
else:
    do_expensive_calculation()
    print("Here you go!")
```

## Using multiple limits

Sometimes you might want to apply different limits to different users. For
example you might want to allow 10 requests per 10 seconds for free users, but
60 requests per 10 seconds for paid users.

Here's how you could do that:

```python
from upstash_ratelimit import Ratelimit, SlidingWindow
from upstash_redis import Redis

class MultiRL:
    def __init__(self) -> None:
        redis = Redis.from_env()
        self.free = Ratelimit(
            redis=redis,
            limiter=SlidingWindow(max_requests=10, window=10),
            prefix="ratelimit:free",
        )

        self.paid = Ratelimit(
            redis=redis,
            limiter=SlidingWindow(max_requests=60, window=10),
            prefix="ratelimit:paid",
        )

# Create a new ratelimiter, that allows 10 requests per 10 seconds
ratelimit = MultiRL()

ratelimit.free.limit("userIP")
ratelimit.paid.limit("userIP")
```

## Custom Rates

When rate limiting, you may want different requests to consume different amounts of tokens.
This could be useful when processing batches of requests where you want to rate limit based
on items in the batch or when you want to rate limit based on the number of tokens.

To achieve this, you can simply pass `rate` parameter when calling the limit method:

```python

from upstash_ratelimit import Ratelimit, FixedWindow
from upstash_redis import Redis

ratelimit = Ratelimit(
    redis=Redis.from_env(),
    limiter=FixedWindow(max_requests=10, window=10),
)

# pass rate as 5 to subtract 5 from the number of
# allowed requests in the window:
identifier = "api"
response = ratelimit.limit(identifier, rate=5)
```

# Getting Started
Source: https://upstash.com/docs/redis/sdks/ratelimit-py/gettingstarted

## Install

```bash
pip install upstash-ratelimit
```

## Create database

To be able to use upstash-ratelimit, you need to create a database on
[Upstash](https://console.upstash.com/).

## Usage

For possible Redis client configurations, have a look at the
[Redis SDK repository](https://github.com/upstash/redis-python).

> This library supports asyncio as well. To use it, import the asyncio-based
> variant from the `upstash_ratelimit.asyncio` module.

```python
from upstash_ratelimit import Ratelimit, FixedWindow
from upstash_redis import Redis

# Create a new ratelimiter, that allows 10 requests per 10 seconds
ratelimit = Ratelimit(
    redis=Redis.from_env(),
    limiter=FixedWindow(max_requests=10, window=10),
    # Optional prefix for the keys used in Redis. This is useful
    # if you want to share a Redis instance with other applications
    # and want to avoid key collisions. The default prefix is
    # "@upstash/ratelimit"
    prefix="@upstash/ratelimit",
)

# Use a constant string to limit all requests with a single ratelimit
# Or use a user ID, API key or IP address for individual limits.
identifier = "api"
response = ratelimit.limit(identifier)

if not response.allowed:
    print("Unable to process at this time")
else:
    do_expensive_calculation()
    print("Here you go!")
```

The `limit` method also returns the following metadata:

```python
@dataclasses.dataclass
class Response:
    allowed: bool
    """
    Whether the request may pass(`True`) or exceeded the limit(`False`)
    """

    limit: int
    """
    Maximum number of requests allowed within a window.
    """

    remaining: int
    """
    How many requests the user has left within the current window.
    """

    reset: float
    """
    Unix timestamp in seconds when the limits are reset
    """
```

# Overview
Source: https://upstash.com/docs/redis/sdks/ratelimit-py/overview

`upstash-ratelimit` is a connectionless rate limiting library for Python,
designed to be used in serverless environments such as:

* AWS Lambda
* Vercel Serverless
* Google Cloud Functions
* and other environments where HTTP is preferred over TCP.

The SDK is currently compatible with Python 3.8 and above.

You can find the Github Repository [here](https://github.com/upstash/ratelimit-python).

# Ratelimiting Algorithms
Source: https://upstash.com/docs/redis/sdks/ratelimit-ts/algorithms

We provide different algorithms to use out of the box. Each has pros and cons.

## Fixed Window

This algorithm divides time into fixed durations/windows. For example each
window is 10 seconds long. When a new request comes in, the current time is used
to determine the window and a counter is increased. If the counter is larger
than the set limit, the request is rejected.

<Tip>
  In fixed & sliding window algorithms, the reset time is based on fixed time boundaries (which depend on the period), not on when the first request was made. So two requests made right before the window ends still count toward the current window, and limits reset at the start of the next window.
</Tip>

### Pros

* Very cheap in terms of data size and computation
* Newer requests are not starved due to a high burst in the past
* Supports [dynamic limits](/docs/redis/sdks/ratelimit-ts/features#dynamic-limits)

### Cons

* Can cause high bursts at the window boundaries to leak through
* Causes request stampedes if many users are trying to access your server,
  whenever a new window begins

### Usage

Create a new ratelimiter, that allows 10 requests per 10 seconds.

<Tabs>
  <Tab title="Regional">
    ```ts
    const ratelimit = new Ratelimit({
      redis: Redis.fromEnv(),
      limiter: Ratelimit.fixedWindow(10, "10 s"),
    });
    ```
  </Tab>
  <Tab title="Multi Regional">
    ```ts
    const ratelimit = new MultiRegionRatelimit({
      redis: [
        new Redis({
          /* auth */
        }),
        new Redis({
          /* auth */
        })
      ],
      limiter: MultiRegionRatelimit.fixedWindow(10, "10 s"),
    });
    ```
  </Tab>
</Tabs>

## Sliding Window

Builds on top of fixed window but instead of a fixed window, we use a rolling
window. Take this example: We have a rate limit of 10 requests per 1 minute. We
divide time into 1 minute slices, just like in the fixed window algorithm.
Window 1 will be from 00:00:00 to 00:01:00 (HH:MM:SS). Let's assume it is
currently 00:01:15 and we have received 4 requests in the first window and 5
requests so far in the current window. The approximation to determine if the
request should pass works like this:

```ts
limit = 10

// 4 request from the old window, weighted + requests in current window
rate = 4 * ((60 - 15) / 60) + 5 = 8

return rate < limit // True means we should allow the request
```

### Pros

* Solves the issue near boundary from fixed window.
* Supports [dynamic limits](/docs/redis/sdks/ratelimit-ts/features#dynamic-limits)

### Cons

* More expensive in terms of storage and computation
* Is only an approximation, because it assumes a uniform request flow in the
  previous window, but this is fine in most cases

### Usage

Create a new ratelimiter, that allows 10 requests per 10 seconds.

<Tabs>
  <Tab title="Regional">
    ```ts
    const ratelimit = new Ratelimit({
      redis: Redis.fromEnv(),
      limiter: Ratelimit.slidingWindow(10, "10 s"),
    });
    ```
  </Tab>
  <Tab title="Multi Regional">
    **Warning:** Using sliding window algorithm with the multiregion setup results in large number of
    commands in Redis and long request processing times. If you want to keep the number of commands
    low, we recommend using the [fixed window algorithm in multi region setup](/docs/redis/sdks/ratelimit-ts/algorithms#fixed-window).

    ```ts
    const ratelimit = new MultiRegionRatelimit({
      redis: [
        new Redis({
          /* auth */
        }),
        new Redis({
          /* auth */
        })
      ],
      limiter: MultiRegionRatelimit.slidingWindow(10, "10 s"),
    });
    ```

  </Tab>
</Tabs>

<Tip>
  `reset` field in the [`limit`](/docs/redis/sdks/ratelimit-ts/methods#limit) and [`getRemaining`](/docs/redis/sdks/ratelimit-ts/methods#getremaining) methods of sliding window do not
  provide an exact reset time. Instead, the reset time is the start time of
  the next window.
</Tip>

## Token Bucket

Consider a bucket filled with `{maxTokens}` tokens that refills constantly at
`{refillRate}` per `{interval}`. Every request will remove one token from the
bucket and if there is no token to take, the request is rejected.

### Pros

* Bursts of requests are smoothed out and you can process them at a constant
  rate.
* Allows to set a higher initial burst limit by setting `maxTokens` higher than
  `refillRate`
* Supports [dynamic limits](/docs/redis/sdks/ratelimit-ts/features#dynamic-limits)

### Cons

* Expensive in terms of computation

### Usage

Create a new bucket, that refills 5 tokens every 10 seconds and has a maximum
size of 10.

<Tabs>
  <Tab title="Regional">
    ```ts
    const ratelimit = new Ratelimit({
      redis: Redis.fromEnv(),
      limiter: Ratelimit.tokenBucket(5, "10 s", 10),
      analytics: true,
    });
    ```
  </Tab>

  <Tab title="Multi Regional">
    _Not yet supported for `MultiRegionRatelimit`_
  </Tab>
</Tabs>

# Costs
Source: https://upstash.com/docs/redis/sdks/ratelimit-ts/costs

This page details the cost of the Ratelimit algorithms in terms of the number of Redis commands. Note that these are calculated for Regional Ratelimits. For [Multi Region Ratelimit](/docs/redis/sdks/ratelimit-ts/features#multi-region), costs will be higher. Additionally, if a Global Upstash Redis is used as the database, number of commands should be calculated as `(1+readRegionCount) * writeCommandCount + readCommandCount` and plus 1 if analytics is enabled.

The Rate Limit SDK minimizes Redis calls to reduce latency overhead and cost. Number of commands executed by the Rate Limit algorithm depends on the chosen algorithm, as well as the state of the algorithm and the caching.

#### Algorithm State

By state of the algorithm, we refer to the entry in our Redis store regarding some identifier `ip1`. You can imagine that there is a state for every identifier. We name these states in the following manner for the purpose of attributing costs to each one:

| State        | Success | Explanation                                                              |
| ------------ | ------- | ------------------------------------------------------------------------ |
| First        | true    | First time the Ratelimit was called with identifier `ip1`                |
| Intermediate | true    | Second or some other time the Ratelimit was called with identifier `ip1` |
| Rate-Limited | false   | Requests with identifier `ip1` which are rate limited.                   |

For instance, first time we call the algorithm with `ip1`, `PEXPIRE` is called so that the key expires after some time. In the following calls, we still use the same script but don't call `PEXPIRE`. In the rate-limited state, we may avoid using Redis altogether if we can make use of the cache.

#### Cache Result

We distinguish the two cases when the identifier `ip1` is found in cache, resulting in a "hit" and the case when the identifier `ip1` is not found in the cache, resulting in a "miss". The cache only exists in the runtime environment and is independent of the Redis database. The state of the cache is especially relevant for serverless contexts, where the cache will usually be empty because of a cold start.

| Result | Explanation                                                                                             |
| ------ | ------------------------------------------------------------------------------------------------------- |
| Hit    | Identifier `ip1` is found in the runtime cache                                                          |
| Miss   | Identifier `ip1` is not found in cache or the value in the cache doesn't block (rate-limit) the request |

An identifier is saved in the cache only when a request is rate limited after a call to the Redis database. The request to Redis returns a timestamp for the time when such a request won't be rate limited anymore. We save this timestamp in the cache and this allows us to reject any request before this timestamp without having to consult the Redis database.

See the [section on caching](/docs/redis/sdks/ratelimit-ts/features) for more details.

# Costs

### `limit()`

#### Fixed Window

| Cache Result | Algorithm State | Command Count | Commands            |
| ------------ | --------------- | ------------- | ------------------- |
| Hit/Miss     | First           | 3             | EVAL, INCR, PEXPIRE |
| Hit/Miss     | Intermediate    | 2             | EVAL, INCR          |
| Miss         | Rate-Limited    | 2             | EVAL, INCR          |
| Hit          | Rate-Limited    | 0             | _utilized cache_    |

#### Sliding Window

| Cache Result | Algorithm State | Command Count | Commands                      |
| ------------ | --------------- | ------------- | ----------------------------- |
| Hit/Miss     | First           | 5             | EVAL, GET, GET, INCR, PEXPIRE |
| Hit/Miss     | Intermediate    | 4             | EVAL, GET, GET, INCR          |
| Miss         | Rate-Limited    | 3             | EVAL, GET, GET                |
| Hit          | Rate-Limited    | 0             | _utilized cache_              |

#### Token Bucket

| Cache Result | Algorithm State    | Command Count | Commands                    |
| ------------ | ------------------ | ------------- | --------------------------- |
| Hit/Miss     | First/Intermediate | 4             | EVAL, HMGET, HSET, PEXPIRE |
| Miss         | Rate-Limited       | 2             | EVAL, HMGET                 |
| Hit          | Rate-Limited       | 0             | _utilized cache_            |

### `getRemaining()`

This method doesn't use the cache or it doesn't have a state it depends on. Therefore, every call
results in the same number of commands in Redis.

| Algorithm      | Command Count | Commands       |
| -------------- | ------------- | -------------- |
| Fixed Window   | 2             | EVAL, GET      |
| Sliding Window | 3             | EVAL, GET, GET |
| Token Bucket   | 2             | EVAL, HMGET    |

### `resetUsedTokens()`

This method starts with a `SCAN` command and deletes every key that matches with `DEL` commands:

| Algorithm      | Command Count | Commands             |
| -------------- | ------------- | -------------------- |
| Fixed Window   | 3             | EVAL, SCAN, DEL      |
| Sliding Window | 4             | EVAL, SCAN, DEL, DEL |
| Token Bucket   | 3             | EVAL, SCAN, DEL      |

### `blockUntilReady()`

Works the same as `limit()`.

# Deny List

Enabling deny lists introduces a cost of 2 additional command per `limit` call.

Values passed in `identifier`, `ip`, `userAgent` and `country` are checked with a single `SMISMEMBER` command.
The other command is TTL which is for checking the status of the current ip deny list to figure out whether
it is expired, valid or disabled.

If [Auto IP deny list](/docs/redis/sdks/ratelimit-ts/features#auto-ip-deny-list) is enabled,
the Ratelimit SDK will update the ip deny list everyday, in the first `limit` invocation after 2 AM UTC.
This will consume 9 commands per day.

If a value is found in the deny list at redis, the client saves this value in the cache and denies
any further requests with that value for a minute without calling Redis (except for analytics).

# Analytics

If analytics is enabled, all calls of `limit` will result in 1 more command since `ZINCRBY` will be called to update the analytics.

# Dynamic Limits

When [dynamic limits](/docs/redis/sdks/ratelimit-ts/features#dynamic-limits) are enabled, each `limit` and `getRemaining` call will execute one additional command.

Both `setDynamicLimit` and `getDynamicLimit` execute 1 command each.

# Features
Source: https://upstash.com/docs/redis/sdks/ratelimit-ts/features

## Caching

For extreme load or denial of service attacks, it might be too expensive to call
redis for every incoming request, just to find out it should be blocked because
they have exceeded the limit.

You can use an ephemeral in memory cache by passing some variable of type
`Map<string, number>` as the `ephemeralCache` option:

```ts
const cache = new Map(); // must be outside of your serverless function handler

// ...

const ratelimit = new Ratelimit({
  // ...
  ephemeralCache: cache,
});
```

By default, `ephemeralCache` will be initialized with `new Map()` if no value is provided
as the `ephemeralCache` parameter. To disable the cache, one must pass `ephemeralCache: false`.

If enabled, the ratelimiter will keep track of the blocked identifiers and their
reset timestamps. When a request is received with some identifier `ip1` before the reset time of
`ip1`, the request will be denied without having to call Redis. [`reason` field of the
limit response will be `cacheBlock`](/docs/redis/sdks/ratelimit-ts/methods#limit)

In serverless environments this is only possible if you create the cache or ratelimiter
instance outside of your handler function. While the function is still hot, the
ratelimiter can block requests without having to request data from Redis, thus
saving time and money.

See the section on how caching impacts the cost in the
[costs page](/docs/redis/sdks/ratelimit-ts/costs#cache-result).

## Timeout

You can define an optional timeout in milliseconds, after which the request will
be allowed to pass regardless of what the current limit is. This can be useful
if you don't want network issues to cause your application to reject requests.

```ts
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "10 s"),
  timeout: 1000, // 1 second
  analytics: true,
});
```

Default value of the timeout is 5 seconds if no `timeout` is provided. When response
is success because of a timeout, this is shown in
[the `reason` field of the limit method](/docs/redis/sdks/ratelimit-ts/methods#limit).

## Analytics & Dashboard

Another feature of the rate limiting algorithm is to collect analytics.

By default, analytics is disabled. To enable analytics, simply set the `analytics` parameter to `true`:

```js
const ratelimit = new Ratelimit({
  redis,
  analytics: true,
  limiter: Ratelimit.slidingWindow(60, "10s"),
});
```

Everytime we call `ratelimit.limit()`, analytics will be sent to the Redis database
([see costs page](/docs/redis/sdks/ratelimit-ts/costs#analytics))
and information about the hour, identifier and the number of rate limit success and
failures will be collected. This information can be viewed from the Upstash console.

If you are using rate limiting in Cloudflare Workers, Vercel Edge or a similar environment,
you need to make sure that the analytics request is delivered correctly to the Redis.
Otherwise, you may observe lower numbers than the actual number of calls.

To make sure that the request completes, you can use the `pending` field returned by
the `limit` method. See the
[Asynchronous synchronization between databases](/docs/redis/sdks/ratelimit-ts/features#asynchronous-synchronization-between-databases)
section to see how `pending` can be used.

### Dashboard

If the analytics is enabled, you can find information about how many requests were made
with which identifiers and how many of the requests were blocked from the [Rate Limit
dashboard in Upstash Console](https://console.upstash.com/ratelimit).

To find the dashboard, simply click the three dots and choose the "Rate Limit Analytics" tab:

![navigate.png]()

In the dashboard, you can find information on how many requests were accepted, how many were blocked
and how many were received in total. Additionally, you can see requests over time; top allowed, rate limited
and denied requests.

![dashboard.png]()

**Allowed requests** show the identifiers of the requests which succeeded. **Rate limited requests** show the
identifiers of the requests which were blocked because they surpassed the limit. **Denied requests** show the identifier,
user agent, country, or the IP address which caused the request to fail.

If you are using a custom prefix, you need to use the same in the dashboard’s top left corner.

## Using Multiple Limits

Sometimes you might want to apply different limits to different users. For
example you might want to allow 10 requests per 10 seconds for free users, but
60 requests per 10 seconds for paid users.

Here's how you could do that:

```ts
import { Redis } from "@upstash/redis";
import { Ratelimit } from "@upstash/ratelimit";

const redis = Redis.fromEnv();

const ratelimit = {
  free: new Ratelimit({
    redis,
    analytics: true,
    prefix: "ratelimit:free",
    limiter: Ratelimit.slidingWindow(10, "10s"),
  }),
  paid: new Ratelimit({
    redis,
    analytics: true,
    prefix: "ratelimit:paid",
    limiter: Ratelimit.slidingWindow(60, "10s"),
  }),
};

await ratelimit.free.limit(ip);
// or for a paid user you might have an email or userId available:
await ratelimit.paid.limit(userId);
```

## Custom Rates

When we call `limit`, it subtracts 1 from the number of calls/tokens available in
the timeframe by default. But there are use cases where we may want to subtract different
numbers depending on the request.

Consider a case where we receive some input from the user either alone or in batches.
If we want to rate limit based on the number of inputs the user can send, we need a way of
specifying what value to subtract.

This is possible thanks to the `rate` parameter. Simply call the `limit` method like the
following:

```ts
const { success } = await ratelimit.limit("identifier", { rate: batchSize });
```

This way, the algorithm will subtract `batchSize` instead of 1.

## Multi Region

Let's assume you have customers in the US and Europe. In this case you can
create 2 separate global redis databases on [Upstash](https://console.upstash.com)
(one with its primary in US and the other in Europe) and your users will enjoy
the latency of whichever db is closest to them.

Using a single Redis instance with replicas in different regions cannot offer
the same performance as `MultiRegionRatelimit` because all write commands have
to go through the primary, increasing latency in other regions.

Using a single redis instance has the downside of providing low latencies only
to the part of your userbase closest to the deployed db. That's why we also
built `MultiRegionRatelimit` which replicates the state across multiple redis
databases as well as offering lower latencies to more of your users.

`MultiRegionRatelimit` does this by checking the current limit in the closest db
and returning immediately. Only afterwards will the state be asynchronously
replicated to the other databases leveraging
[CRDTs](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type). Due
to the nature of distributed systems, there is no way to guarantee the set
ratelimit is not exceeded by a small margin. This is the tradeoff for reduced
global latency.

### Usage

The API is the same, except for asking for multiple redis instances:

```ts
import { MultiRegionRatelimit } from "@upstash/ratelimit"; // for deno: see above
import { Redis } from "@upstash/redis";

// Create a new ratelimiter, that allows 10 requests per 10 seconds
const ratelimit = new MultiRegionRatelimit({
  redis: [
    new Redis({
      /* auth */
    }),
    new Redis({
      /* auth */
    }),
    new Redis({
      /* auth */
    }),
  ],
  limiter: MultiRegionRatelimit.slidingWindow(10, "10 s"),
  analytics: true,
});

// Use a constant string to limit all requests with a single ratelimit
// Or use a userID, apiKey or ip address for individual limits.
const identifier = "api";
const { success } = await ratelimit.limit(identifier);
```

### Asynchronous synchronization between databases

The MultiRegion setup will do some synchronization between databases after
returning the current limit. This can lead to problems on Cloudflare Workers and
therefore Vercel Edge functions, because dangling promises must be taken care
of:

```ts
const { pending } = await ratelimit.limit("id");
context.waitUntil(pending);
```

See more information on `context.waitUntil` at
[Cloudflare](https://developers.cloudflare.com/workers/runtime-apis/context/#waituntil)
and [Vercel](https://vercel.com/docs/functions/edge-middleware/middleware-api#waituntil).
You can also utilize [`waitUntil` from Vercel Functions API](https://vercel.com/docs/functions/functions-api-reference#waituntil).

## Dynamic Limits

Dynamic limits allow you to change rate limits at runtime without recreating the rate limiter instance. This is useful when you need to adjust limits based on user tiers, system load, or other dynamic factors.

To enable dynamic limits, set the `dynamicLimits` option to `true` when creating the rate limiter:

```ts
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "10s"),
  dynamicLimits: true, // Enable dynamic limits
});

// Set a global dynamic limit
await ratelimit.setDynamicLimit({ limit: 5 });

// All subsequent limit checks use the new limit
const { success } = await ratelimit.limit("user_123");

// Get current dynamic limit
const currentLimit = await ratelimit.getDynamicLimit(); // returns 5

// Remove dynamic limit (falls back to default)
await ratelimit.setDynamicLimit({ limit: false });
```

Dynamic limits work with the following single region algorithms: `fixedWindow`, `slidingWindow`, `tokenBucket`, Multi region algrorithms aren't supported.

# Getting Started
Source: https://upstash.com/docs/redis/sdks/ratelimit-ts/gettingstarted

## Create your Redis instance

For the rate limit to work, we need to create an Upstash Redis and get its credentials. To create an Upstash Redis, you can follow the [Upstash Redis "Get Started" guide](/docs/redis/overall/getstarted).

## Add Ratelimit to Your Project

Once we have a Redis instance, next step is adding the rate limit to your project in its most basic form.

### Install Ratelimit

First, we need to install `@upstash/ratelimit`:

<Tabs>
  <Tab title="npm">
    ```bash
    npm install @upstash/ratelimit
    ```
  </Tab>
  <Tab title="deno">
    ```ts
    import { Ratelimit } from "https://cdn.skypack.dev/@upstash/ratelimit@latest";
    ```
  </Tab>
</Tabs>

### Add Ratelimit to Your Endpoint

Next step is to add Ratelimit to your endpoint. In the example below, you can see how to initialize a Ratelimit and use it:

```ts
import { Ratelimit } from "@upstash/ratelimit"; // for deno: see above
import { Redis } from "@upstash/redis";

// Create a new ratelimiter, that allows 10 requests per 10 seconds
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "10 s"),
  analytics: true,
  /**
   * Optional prefix for the keys used in redis. This is useful if you want to share a redis
   * instance with other applications and want to avoid key collisions. The default prefix is
   * "@upstash/ratelimit"
   */
  prefix: "@upstash/ratelimit",
});

// Use a constant string to limit all requests with a single ratelimit
// Or use a userID, apiKey or ip address for individual limits.
const identifier = "api";
const { success } = await ratelimit.limit(identifier);

if (!success) {
  return "Unable to process at this time";
}
doExpensiveCalculation();
return "Here you go!";
```

For Cloudflare Workers and Fastly Compute@Edge, you can use the following imports:

```ts
import { Redis } from "@upstash/redis/cloudflare"; // for cloudflare workers and pages
import { Redis } from "@upstash/redis/fastly"; // for fastly compute@edge
```

In this example, we initialize a Ratelimit with an Upstash Redis. The Uptash Redis instance is created from the environment variables and passed to the Ratelimit instance. Then, we check the access rate using the `ratelimit.limit(identifier)` method. If the `success` field is true, we allow the expensive calculation to go through.

For more examples, see the [Examples](/docs/redis/sdks/ratelimit-ts/overview#examples).

### Set Environment Variables

Final step is to update the environment variables so that the Ratelimit can communicate with the Upstash Redis. `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` environment variables must be set in order for the `Redis.fromEnv()` command to work. You can get the values of these environment variables from the [Upstash Console](https://console.upstash.com/redis) by navigating to the page of the Redis instance you created.

An alternative of using the `Redis.fromEnv()` method is to pass the variables yourself. This can be useful if you save these environment variables with a different name:

```ts
new Redis({
  url: "https://****.upstash.io",
  token: "********",
});
```

Here is how you can set the environment variables in different cases:

<Tabs>
  <Tab title="Vercel">
    Go to the "Settings" tab in your project. In the menu to the left, click "Environment Variables". Add `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` environment variables and their values.
  </Tab>
  <Tab title="Cloudflare Worker (Wrangler)">
    Run:
    ```
    npx wrangler secret put UPSTASH_REDIS_REST_URL
    ```
    When prompted, enter the value of `UPSTASH_REDIS_REST_URL` when prompted. Do the same for `UPSTASH_REDIS_REST_TOKEN`:
    ```
    npx wrangler secret put UPSTASH_REDIS_REST_TOKEN
    ```
  </Tab>
  <Tab title="Local Nextjs Project">
    Go to the `.env.local` file and add the environment variables:
    ```
    UPSTASH_REDIS_REST_URL=****
    UPSTASH_REDIS_REST_TOKEN=****
    ```
  </Tab>
</Tabs>

## Serverless Environments

When we use ratelimit in a serverless environment like CloudFlare Workers or Vercel Edge,
we need to be careful about making sure that the rate limiting operations complete correctly
before the runtime ends after returning the response.

This is important in two cases where we do some operations in the background asynchronously after `limit` is called:

1. Using MultiRegion: synchronize Redis instances in different regions
2. Enabling analytics: send analytics to Redis

In these cases, we need to wait for these operations to finish before sending the response to the user. Otherwise, the runtime will end and we won't be able to complete our chores.

In order to wait for these operations to finish, use the `pending` promise:

```ts
const { pending } = await ratelimit.limit("id");
context.waitUntil(pending);
```

See more information on `context.waitUntil` at
[Cloudflare](https://developers.cloudflare.com/workers/runtime-apis/context/#waituntil)
and [Vercel](https://vercel.com/docs/functions/edge-middleware/middleware-api#waituntil).
You can also utilize [`waitUntil` from Vercel Functions API](https://vercel.com/docs/functions/functions-api-reference#waituntil).

## Customizing the Ratelimit Algorithm

There are several algorithms we can use for rate limiting. Explore the different rate-limiting algorithms available; how they work, their advantages and disadvantages in the [Algorithms page](/docs/redis/sdks/ratelimit-ts/algorithms). You can learn about the **cost in terms of the number of commands**, by referring to the [Costs page](/docs/redis/sdks/ratelimit-ts/costs).

## Methods

In our example, we only used the `limit` method. There are other methods we can use in the Ratelimit. These are:

* `blockUntilReady`: Process a request only when the rate-limiting algorithm allows it.
* `resetUsedTokens`: Reset the rate limiter state for some identifier.
* `getRemaining`: Get the remaining tokens/requests left for some identifier.

To learn more about these methods, refer to the [Methods page](/docs/redis/sdks/ratelimit-ts/methods).

## Features

To configure the your Ratelimit according to your needs, you can make use of several features:

<CardGroup cols={2}>
  <Card
    title="Caching"
    icon="shield-halved"
    href="/redis/sdks/ratelimit-ts/features#caching"
  >
    Handle blocked requests without having to call your Redis Database
  </Card>
  <Card
    title="Timeout"
    icon="stopwatch"
    href="/redis/sdks/ratelimit-ts/features#timeout"
  >
    If the Redis call of the ratelimit is not resolved in some timeframe, allow
    the request by default
  </Card>
  <Card
    title="Analytics & Dashboard"
    icon="magnifying-glass-chart"
    href="/redis/sdks/ratelimit-ts/features#analytics-and-dashboard"
  >
    Collect information on which identifiers made how many requests and how many
    were blocked
  </Card>
  <Card
    title="Traffic Protection"
    icon="lock"
    href="/redis/sdks/ratelimit-ts/traffic-protection"
  >
    Create a deny list to block requests based on user agents, countries, IP
    addresses an more
  </Card>
  <Card
    title="Custom Rates"
    icon="chart-simple"
    href="/redis/sdks/ratelimit-ts/features#custom-rates"
  >
    Consume different amounts of tokens in different requests (example: limiting
    based on request/response size)
  </Card>
  <Card
    title="Multi Region"
    icon="globe"
    href="/redis/sdks/ratelimit-ts/features#multi-region"
  >
    Utilize several Redis databases in different regions to serve users faster
  </Card>
  <Card
    title="Multiple Limits"
    icon="gears"
    href="/redis/sdks/ratelimit-ts/features#using-multiple-limits"
  >
    Use different limits for different kinds of requests (example: paid and free
    users)
  </Card>
  <Card
    title="Dynamic Limits"
    icon="sliders"
    href="/redis/sdks/ratelimit-ts/features#dynamic-limits"
  >
    Change rate limits at runtime without recreating the rate limiter instance
  </Card>
</CardGroup>

For more information about the features, see the [Features page](/docs/redis/sdks/ratelimit-ts/features).

# Configure Upstash Ratelimit Strapi Plugin
Source: https://upstash.com/docs/redis/sdks/ratelimit-ts/integrations/strapi/configurations

After setting up the plugin, it's possible to customize the ratelimiter algorithm and rates. You can also define different rate limits and rate limit algorithms for different routes.

## General Configurations

<ParamField path="enabled" type="boolean" default="true">
  Enable or disable the plugin.
</ParamField>

## Database Configurations

<ParamField path="token" type="string" required>
  The token to authenticate with the Upstash Redis REST API. You can find this
  credential on Upstash Console with the name `UPSTASH_REDIS_REST_TOKEN`
</ParamField>
<ParamField path="url" type="string" required>
  The URL for the Upstash Redis REST API. You can find this credential on
  Upstash Console with the name `UPSTASH_REDIS_REST_URL`
</ParamField>

<ParamField path="prefix" type="string" default="@strapi">
  The prefix for the rate limit keys. The plugin uses this prefix to store the
  rate limit data in Redis. <br />
  For example, if the prefix is `@strapi`, the key will be
  `@strapi:<method>:<route>:<identifier>`.
</ParamField>

<ParamField path="analytics" type="boolean" default="false">
  Enable analytics for the rate limit. When enabled, the plugin extra insights
  related to your ratelimits. You can use this data to analyze the rate limit
  usage on [Upstash Console](https://console.upstash.com/ratelimit).
</ParamField>

## Strategy

The plugin uses a strategy array to define the rate limits per route. Each strategy object has the following properties:

<ParamField
  path="methods"
  type="('GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH' |'ALL')[]"
  required
>
  An array of HTTP methods to apply the rate limit. <br />
  For example, `["GET", "POST"]`
</ParamField>

<ParamField path="path" type="string" required>
  The path to apply the rate limit. You can use wildcards to match multiple
  routes. For example, `*` matches all routes. <br />
  Some examples: <br />
  * `path: "/api/restaurants/:id"` <br />
  * `path: "/api/restaurants"` <br />
</ParamField>

<ParamField path="identifierSource" type="string" required>
  The source to identifiy the user. Requests with the same identifier will be
  rate limited under the same limit. <br />
  Available sources are: <br />
  * `ip`: The IP address of the user. <br />
  * `header`: The value of a header key. You should pass the source in the `header.<HEADER_KEY>` format. <br />
  For example, `header.Authorization` will use the value of the `Authorization`
</ParamField>

<ParamField path="debug" type="string">
  Enable debug mode for the route. When enabled, the plugin logs the remaining
  limits and the block status for each request. <br />
</ParamField>

<ParamField path="limiter" type="object" required>
  The limiter configuration for the route. The limiter object has the following
  properties:

  <Card>
	  <ParamField path="algorithm" type="'fixed-window' | 'sliding-window' | 'token-bucket'" required>
		The rate limit algorithm to use. For more information related to algorithms, see docs [**here**](/docs/redis/sdks/ratelimit-ts/algorithms). <br />
		* `fixed-window`: The fixed-window algorithm divides time into fixed intervals. Each interval has a set limit of allowed requests. When a new interval starts, the count resets. <br />
		* `sliding-window`:
The sliding-window algorithm uses a rolling time frame. It considers requests from the past X time units, continuously moving forward. This provides a smoother distribution of requests over time. <br />
		* `token-bucket`: The token-bucket algorithm uses a bucket that fills with tokens at a steady rate. Each request consumes a token. If the bucket is empty, requests are denied. This allows for bursts of traffic while maintaining a long-term rate limit.<br />
	  </ParamField>
	  <ParamField path="tokens" type="number" required>
	  	The number of tokens allowed in the time window. <br />
	  </ParamField>
	  <ParamField path="window" type="string" required>
	  	The time window for the rate limit. Available units are `"ms" | "s" | "m" | "h" | "d"` <br />
	  	For example, `20s` means 20 seconds.
	  </ParamField>
	  <ParamField path="refillRate" type="number">
	  	The rate at which the bucket refills. **This property is only used for the token-bucket algorithm.** <br />
	  </ParamField>
  </Card>
</ParamField>

## Examples

<CodeGroup>
```json Apply rate limit for all routes
{
   "strapi-plugin-upstash-ratelimit":{
      "enabled":true,
      "resolve":"./src/plugins/strapi-plugin-upstash-ratelimit",
      "config":{
         "enabled":true,
         "token":"process.env.UPSTASH_REDIS_REST_TOKEN",
         "url":"process.env.UPSTASH_REDIS_REST_URL",
         "strategy":[
            {
               "methods":[
                  "GET",
                  "POST"
               ],
               "path":"*",
               "identifierSource":"header.Authorization",
               "limiter":{
                  "algorithm":"fixed-window",
                  "tokens":10,
                  "window":"20s"
               }
            }
         ],
         "prefix":"@strapi"
      }
   }
}
```

```json Apply rate limit with IP
{
  "strapi-plugin-upstash-ratelimit": {
    "enabled": true,
    "resolve": "./src/plugins/strapi-plugin-upstash-ratelimit",
    "config": {
      "enabled": true,
      "token": "process.env.UPSTASH_REDIS_REST_TOKEN",
      "url": "process.env.UPSTASH_REDIS_REST_URL",
      "strategy": [
        {
          "methods": ["GET", "POST"],
          "path": "*",
          "identifierSource": "ip",
          "limiter": {
            "algorithm": "fixed-window",
            "tokens": 10,
            "window": "20s"
          }
        }
      ],
      "prefix": "@strapi"
    }
  }
}
```

```json Routes with different rate limit algorithms
{
  "strapi-plugin-upstash-ratelimit": {
    "enabled": true,
    "resolve": "./src/plugins/strapi-plugin-upstash-ratelimit",
    "config": {
      "enabled": true,
      "token": "process.env.UPSTASH_REDIS_REST_TOKEN",
      "url": "process.env.UPSTASH_REDIS_REST_URL",
      "strategy": [
        {
          "methods": ["GET", "POST"],
          "path": "/api/restaurants/:id",
          "identifierSource": "header.x-author",
          "limiter": {
            "algorithm": "fixed-window",
            "tokens": 10,
            "window": "20s"
          }
        },
        {
          "methods": ["GET"],
          "path": "/api/restaurants",
          "identifierSource": "header.x-author",
          "limiter": {
            "algorithm": "tokenBucket",
            "tokens": 10,
            "window": "20s",
            "refillRate": 1
          }
        }
      ],
      "prefix": "@strapi"
    }
  }
}
```

</CodeGroup>

# Upstash Ratelimit Strapi Integration
Source: https://upstash.com/docs/redis/sdks/ratelimit-ts/integrations/strapi/getting-started

Strapi is an open-source, Node.js based, Headless CMS that saves developers a lot of development time, enabling them to build their application backends quickly by decreasing the lines of code necessary.

You can use Upstash's HTTP and Redis based [Ratelimit package](https://github.com/upstash/ratelimit-js) integration with Strapi to protect your APIs from abuse.

## Getting started

### Installation

<CodeGroup>

```bash npm
npm install --save @upstash/strapi-plugin-upstash-ratelimit
```

```bash yarn
yarn add @upstash/strapi-plugin-upstash-ratelimit
```

</CodeGroup>

### Create database

Create a new redis database on [Upstash Console](https://console.upstash.com/). See [related docs](/docs/redis/overall/getstarted) for further info related to creating a database.

### Set up environment variables

Get the environment variables from [Upstash Console](https://console.upstash.com/), and set it to `.env` file as below:

```shell .env
UPSTASH_REDIS_REST_TOKEN="<YOUR_TOKEN>"
UPSTASH_REDIS_REST_URL="<YOUR_URL>"
```

### Configure the plugin

You can use

<CodeGroup>
```typescript /config/plugins.ts
export default () => ({
  "strapi-plugin-upstash-ratelimit": {
    enabled: true,
    resolve: "./src/plugins/strapi-plugin-upstash-ratelimit",
    config: {
      enabled: true,
      token: process.env.UPSTASH_REDIS_REST_TOKEN,
      url: process.env.UPSTASH_REDIS_REST_URL,
      strategy: [
        {
          methods: ["GET", "POST"],
          path: "*",
          limiter: {
            algorithm: "fixed-window",
            tokens: 10,
            window: "20s",
          },
        },
      ],
      prefix: "@strapi",
    },
  },
});
```

```javascript /config/plugins.js
module.exports = () => ({
  "strapi-plugin-upstash-ratelimit": {
    enabled: true,
    resolve: "./src/plugins/strapi-plugin-upstash-ratelimit",
    config: {
      enabled: true,
      token: process.env.UPSTASH_REDIS_REST_TOKEN,
      url: process.env.UPSTASH_REDIS_REST_URL,
      strategy: [
        {
          methods: ["GET", "POST"],
          path: "*",
          limiter: {
            algorithm: "fixed-window",
            tokens: 10,
            window: "20s",
          },
        },
      ],
      prefix: "@strapi",
    },
  },
});
```

</CodeGroup>

# Methods
Source: https://upstash.com/docs/redis/sdks/ratelimit-ts/methods

This page contains information on what methods are available in Ratelimit and how they can be used. For information on the
cost of these operations in term of number of Redis commands, refer to the [Costs page](/docs/redis/sdks/ratelimit-ts/costs).

## `limit`

The `limit` method is the heart of the Ratelimit algorithm.

```ts
ratelimit.limit(
  identifier: string,
  req?: {
    geo?: Geo;
    rate?: number,
    ip?: string,
    userAgent?: string,
    country?: string
  },
): Promise<RatelimitResponse>
```

It receives an identifier to rate limit. Additionally, it can be passed a `req` parameter which can contain either a
`geo` or a `rate` field. `geo` field is passed to the analytics but is not in use currently. The `rate` field determines
the amount of tokens/requests to subtract from the state of the algorithm with regards to the provided identifier.

The `limit` method returns some more metadata that might be useful to you:

````ts
export type RatelimitResponse = {
  /**
   * Whether the request may pass(true) or exceeded the limit(false)
   */
  success: boolean;
  /**
   * Maximum number of requests allowed within a window.
   */
  limit: number;
  /**
   * How many requests the user has left within the current window.
   */
  remaining: number;
  /**
   * Unix timestamp in milliseconds when the limits are reset.
   */
  reset: number;

  /**
   * For the MultiRegion setup we do some synchronizing in the background, after returning the current limit.
   * Or when analytics is enabled, we send the analytics asynchronously after returning the limit.
   * In most case you can simply ignore this.
   *
   * On Vercel Edge or Cloudflare workers, you need to explicitly handle the pending Promise like this:
   *
   * ```ts
   * const { pending } = await ratelimit.limit("id")
   * context.waitUntil(pending)
   * ```
   *
   * See `waitUntil` documentation in
   * [Cloudflare](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/#contextwaituntil)
   * and [Vercel](https://vercel.com/docs/functions/edge-middleware/middleware-api#waituntil)
   * for more details.
   * ```
   */
  pending: Promise<unknown>;

  /**
   * Reason behind the result in `success` field.
   * - Is set to "timeout" when request times out
   * - Is set to "cacheBlock" when an identifier is blocked through cache without calling redis because it was
   *    rate limited previously.
   * - Is set to "denyList" when identifier or one of ip/user-agent/country parameters is in deny list. To enable
   *    deny list, see `enableProtection` parameter. To edit the deny list, see the Upstash Ratelimit Dashboard
   *    at https://console.upstash.com/ratelimit.
   * - Is set to undefined if rate limit check had to use Redis. This happens in cases when `success` field in
   *    the response is true. It can also happen the first time sucecss is false.
   */
  reason?: RatelimitResponseType;

  /**
   * The value which was in the deny list if reason: "denyList"
   */
  deniedValue?: string;
};
````

## `blockUntilReady`

In case you don't want to reject a request immediately but wait until it can be
processed, we also provide

```ts
ratelimit.blockUntilReady(
  identifier: string,
  timeout: number
): Promise<RatelimitResponse>
```

It is very similar to the `limit` method and takes an identifier and returns the
same response. However if the current limit has already been exceeded, it will
automatically wait until the next window starts and will try again. Setting the
timeout parameter (in milliseconds) will cause the returned Promise to resolve
in a finite amount of time.

```ts
// Create a new ratelimiter, that allows 10 requests per 10 seconds
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "10 s"),
  analytics: true,
});

// `blockUntilReady` returns a promise that resolves as soon as the request is allowed to be processed, or after 30 seconds
const { success } = await ratelimit.blockUntilReady("id", 30_000);

if (!success) {
  return "Unable to process, even after 30 seconds";
}
doExpensiveCalculation();
return "Here you go!";
```

<Warning>
  In **Cloudflare**, `blockUntilReady` will not work as intended due to
  `Date.now()` not behaving the same as in Node environments.
  <br />
  <br />
  **For more information, check**: <br />
  https://developers.cloudflare.com/workers/runtime-apis/web-standards
</Warning>

## `resetUsedTokens`

This method resets the state of the algorithm with respect to some identifier:

```ts
ratelimit.resetUsedTokens(identifier: string): Promise<void>
```

## `getRemaining`

This method returns the remaining tokens/requests available for some identifier:

```ts
ratelimit.getRemaining(identifier: string): Promise<{
  remaining: number;
  reset: number;
}>
```

`remaining` is the remaining tokens. `reset` is the reset timestamp.

## `setDynamicLimit`

This method sets or removes a [global dynamic rate limit](/docs/redis/sdks/ratelimit-ts/features#dynamic-limits). Dynamic limits override the default limiter settings for all subsequent rate limit checks.

```ts
ratelimit.setDynamicLimit(options: {
  limit: number | false;
}): Promise<void>
```

**Parameters:**
* `limit`: The new limit value (number), or `false` to remove the dynamic limit and fall back to the default

**Example:**

```ts
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "10s"),
  dynamicLimits: true,
});

// Set a new dynamic limit
await ratelimit.setDynamicLimit({ limit: 5 });

// Remove dynamic limit (falls back to default of 10)
await ratelimit.setDynamicLimit({ limit: false });
```

## `getDynamicLimit`

This method retrieves the current [dynamic limit](/docs/redis/sdks/ratelimit-ts/features#dynamic-limits) if one is set.

```ts
ratelimit.getDynamicLimit(): Promise<{ dynamicLimit: number | null }>
```

**Returns:** The current dynamic limit value, or `null` if no dynamic limit is set

**Example:**

```ts
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "10s"),
  dynamicLimits: true,
});

await ratelimit.setDynamicLimit({ limit: 5 });
const { dynamicLimit } = await ratelimit.getDynamicLimit(); // returns 5
```

# Overview
Source: https://upstash.com/docs/redis/sdks/ratelimit-ts/overview

# Upstash Rate Limit

[![npm (scoped)]()](https://www.npmjs.com/package/@upstash/ratelimit)

It is the only connectionless (HTTP based) rate limiting library and designed
for:

* Serverless functions (AWS Lambda, Vercel ...)
* Cloudflare Workers
* Vercel Edge
* Fastly Compute@Edge
* Next.js, Jamstack ...
* Client side web/mobile applications
* WebAssembly
* and other environments where HTTP is preferred over TCP.

## Quick Links:

* [Github Repository](https://github.com/upstash/ratelimit)
* [Getting Started](/docs/redis/sdks/ratelimit-ts/gettingstarted)
* [Costs](/docs/redis/sdks/ratelimit-ts/costs)

## Features

<CardGroup cols={2}>
  <Card
    title="Caching"
    icon="shield-halved"
    href="/redis/sdks/ratelimit-ts/features#caching"
  >
    Handle blocked requests without having to call your Redis Database
  </Card>
  <Card
    title="Timeout"
    icon="stopwatch"
    href="/redis/sdks/ratelimit-ts/features#timeout"
  >
    If the Redis call of the ratelimit is not resolved in some timeframe, allow
    the request by default
  </Card>
  <Card
    title="Analytics & Dashboard"
    icon="magnifying-glass-chart"
    href="/redis/sdks/ratelimit-ts/features#analytics-and-dashboard"
  >
    Collect information on which identifiers made how many requests and how many
    were blocked
  </Card>
  <Card
    title="Traffic Protection"
    icon="lock"
    href="/redis/sdks/ratelimit-ts/traffic-protection"
  >
    Create a deny list to block requests based on user agents, countries, IP
    addresses and more
  </Card>
  <Card
    title="Custom Rates"
    icon="chart-simple"
    href="/redis/sdks/ratelimit-ts/features#custom-rates"
  >
    Consume different amounts of tokens in different requests (example: limiting
    based on request/response size)
  </Card>
  <Card
    title="Multi Region"
    icon="globe"
    href="/redis/sdks/ratelimit-ts/features#multi-region"
  >
    Utilize several Redis databases in different regions to serve users faster
  </Card>
  <Card
    title="Multiple Limits"
    icon="gears"
    href="/redis/sdks/ratelimit-ts/features#using-multiple-limits"
  >
    Use different limits for different kinds of requests (example: paid and free
    users)
  </Card>
  <Card
    title="Dynamic Limits"
    icon="sliders"
    href="/redis/sdks/ratelimit-ts/features#dynamic-limits"
  >
    Change rate limits at runtime without recreating the rate limiter instance
  </Card>
</CardGroup>

For more information about the features, see the [Features tab](/docs/redis/sdks/ratelimit-ts/features).

## Examples

<CardGroup cols={2}>
  <Card
    title="Nextjs"
    href="https://github.com/upstash/ratelimit/tree/main/examples/nextjs"
  >
    Rate limit an API in a Nextjs project
  </Card>
  <Card
    title="Nextjs with Middleware"
    href="https://github.com/upstash/ratelimit/tree/main/examples/nextjs-middleware"
  >
    Rate limit an API with a Middleware in a Nextjs project
  </Card>
  <Card
    title="Vercel Edge"
    href="https://github.com/upstash/ratelimit/tree/main/examples/vercel-edge"
  >
    Rate limit an Vercel Edge Function
  </Card>
  <Card
    title="Enabling Protection"
    href="https://github.com/upstash/ratelimit/tree/main/examples/enable-protection"
  >
    Use Deny Lists to Protect Your Website
  </Card>
  <Card
    title="Cloudflare Pages"
    href="https://github.com/upstash/ratelimit/tree/main/examples/cloudflare-pages"
  >
    Rate limit access to your Cloudflare Pages app
  </Card>
  <Card
    title="Cloudflare Workers"
    href="https://github.com/upstash/ratelimit/tree/main/examples/cloudflare-workers"
  >
    Rate limit access to your Cloudflare Workers
  </Card>
  <Card
    title="Remix"
    href="https://github.com/upstash/ratelimit/tree/main/examples/remix"
  >
    Rate limit access to a Remix App
  </Card>
  <Card
    title="Rate limit using Vercel KV"
    href="https://github.com/upstash/ratelimit/tree/main/examples/with-vercel-kv"
  >
    Rate limit a Nexjs app using Vercel KV
  </Card>
  <Card
    title="Deno App"
    href="https://github.com/upstash/ratelimit/tree/main/examples/deno"
  >
    Rate limit your deno app
  </Card>
  <Card
    title="Rate limit your Chatbot"
    href="https://upstash.com/blog/degree-guru#rate-limiting"
  >
    Limiting requests to a Chatbot endpoint which streams LLM outputs
  </Card>
</CardGroup>

# Traffic Protection
Source: https://upstash.com/docs/redis/sdks/ratelimit-ts/traffic-protection

### Deny List

Imagine that you want to block requests from certain countries or from some
user agents. In this case, you can make use of deny lists introduced in
ratelimit version 1.2.1.

Deny lists allow you to block based on IP addresses, user agents, countries
and [identifiers](/docs/redis/sdks/ratelimit-ts/methods#limit).

To enable checking the deny list in your Ratelimit client, simply pass
`enableProtection` as `true`:

```tsx
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "10 s"),
  enableProtection: true
  analytics: true,
});
```

When `limit` is called, the client will check whether any of these values
are in the deny list and block the request if so.

```tsx
const { success, pending, reason, deniedValue } = ratelimit.limit("userId", {
  ip: "ip-address",
  userAgent: "user-agent",
  country: "country",
});

await pending; // await pending if you have analytics enabled

console.log(success, reason, deniedValue);
// prints: false, "denyList", "ip-address"
```

If a request fails because a value was in deny list, `reason` field will
be `"denyList"`. `deniedValue` will contain the value in the deny list.
See [limit method](/docs/redis/sdks/ratelimit-ts/methods#limit)
for more detailts.

Client also keeps a **cache** of denied values. When a value is found
in the deny list, the client stores this value in the cache. If this value
is encountered in the following requests, it is **denied without calling
Redis at all**. Items are stored in the cache for a minute. This means that if
you add a new value to the deny list, it will immediately take affect but when you
remove a value, it can take up to a minute for clients to start
accepting the value. This can significantly reduce the number of calls to Redis.

Contents of the deny lists are managed from the [Ratelimit Dashboard](/docs/redis/sdks/ratelimit-ts/features#dashboard).
You can use the dashboard to add items to the deny list or remove them.
If you have analytics enabled, you can also view the number of denied
requests per country/ip address/user agent/identifier on the dashboard.

![denylist.png]()

Note that we look for exact match when checking a value to see if it's in
the deny lists. **Pattern matching is not supported**.

### Auto IP Deny List

The Auto IP Deny List feature enables the automatic blocking of IP addresses
identified as malicious through open-source IP deny lists. This functionality
uses the [ipsum repository on GitHub](https://github.com/stamparm/ipsum),
which aggregates data from over 30 different deny lists.

To enable protection, set the enableProtection parameter to true. Once activated,
your SDK will automatically block IP addresses by leveraging the IP deny lists
when you provide the request IPs in the limit method.

```ts
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "10 s"),
  enableProtection: true,
});
```

The IP deny list is updated daily at 2 AM UTC. Upon expiration, the
first call to limit after 2 AM UTC will trigger an update, downloading
the latest IPs from GitHub and refreshing the list in your Redis
instance. The update process occurs asynchronously, allowing you to
return the result to the user while the IP list updates in the
background. To ensure the update completes successfully, utilize the
pending field:

```ts
const { success, pending } = await ratelimit.limit(
  content,
  {ip: "ip-address"}
);

await pending;
```

For more information on effectively using pending, refer to the
["Asynchronous synchronization between databases" section](/docs/redis/sdks/ratelimit-ts/features#asynchronous-synchronization-between-databases).

Blocked IPs will be listed in the "Denied" section of the Ratelimit
dashboard, providing a clear overview of the addresses that have
been automatically blocked.

If you prefer to disable the Auto IP Deny List feature while still
using the deny lists, you can do so via the [Ratelimit dashboard on
the Upstash Console](https://console.upstash.com/ratelimit).

# Realtime
Source: https://upstash.com/docs/redis/sdks/realtime

# Redis Analytics
Source: https://upstash.com/docs/redis/sdks/redis-analytics

# Advanced
Source: https://upstash.com/docs/redis/sdks/ts/advanced

## Disable automatic serialization

Your data is (de)serialized as `json` by default. This works for most use cases
but you can disable it if you want:

```ts
const redis = new Redis({
  // ...
  automaticDeserialization: false,
});

// or
const redis = Redis.fromEnv({
  automaticDeserialization: false,
});
```

This probably breaks quite a few types, but it's a first step in that direction.
Please report bugs and broken types
[here](https://github.com/upstash/upstash-redis/issues/49).

## Keep-Alive

`@upstash/redis` optimizes performance by reusing connections wherever possible, reducing latency.
This is achieved by keeping the client in memory instead of reinitializing it with each new function invocation.
As a result, when a hot lambda function receives a new request, it uses the already initialized client, allowing for the reuse of existing connections to Upstash.

<Tip>This functionality is enabled by default.</Tip>

## Request Timeout

You can configure the SDK so that it will throw an error if the request takes longer than a specified time.

You can achieve this using the signal parameter like this:

```ts
const redis = new Redis({
  url: "<UPSTASH_REDIS_REST_URL>",
  token: "<UPSTASH_REDIS_REST_TOKEN>",
  // set a timeout of 1 second
  signal: () => AbortSignal.timeout(1000),
});

try {
  await redis.get( ... )
} catch (error) {
  if (error.name === "TimeoutError") {
    console.error("Request timed out");
  } else {
    console.error("An error occurred:", error);
  }
}
```

## Telemetry

This library sends anonymous telemetry data to help us improve your experience.
We collect the following:

* SDK version
* Platform (Deno, Cloudflare, Vercel)
* Runtime version (node@18.x)

You can opt out by setting the `UPSTASH_DISABLE_TELEMETRY` environment variable
to any truthy value.

```sh
UPSTASH_DISABLE_TELEMETRY=1
```

Alternatively, you can pass `enableTelemetry: false` when initializing the Redis client:

```ts
const redis = new Redis({
  // ...,
  enableTelemetry: false,
});
```

- [ECHO](https://upstash.com/docs/redis/sdks/ts/commands/auth/echo.md)
- [PING](https://upstash.com/docs/redis/sdks/ts/commands/auth/ping.md): Send a ping to the server and get a response if the server is alive.
- [BITCOUNT](https://upstash.com/docs/redis/sdks/ts/commands/bitmap/bitcount.md): Count the number of set bits.
- [BITOP](https://upstash.com/docs/redis/sdks/ts/commands/bitmap/bitop.md): Perform bitwise operations between strings.
- [BITPOS](https://upstash.com/docs/redis/sdks/ts/commands/bitmap/bitpos.md): Find the position of the first set or clear bit (bit with a value of 1 or 0) in a Redis string key.
- [GETBIT](https://upstash.com/docs/redis/sdks/ts/commands/bitmap/getbit.md): Retrieve a single bit.
- [SETBIT](https://upstash.com/docs/redis/sdks/ts/commands/bitmap/setbit.md): Set a single bit in a string.
- [CLIENT SETINFO](https://upstash.com/docs/redis/sdks/ts/commands/connection/client_setinfo.md): Set client library name and version information.
- [FCALL](https://upstash.com/docs/redis/sdks/ts/commands/functions/call.md): Invoke a function.
- [FCALL_RO](https://upstash.com/docs/redis/sdks/ts/commands/functions/call_ro.md): Invoke a read-only function
- [FUNCTION DELETE](https://upstash.com/docs/redis/sdks/ts/commands/functions/delete.md): Delete a library and all its functions.
- [FUNCTION FLUSH](https://upstash.com/docs/redis/sdks/ts/commands/functions/flush.md): Delete all the libraries and functions.
- [FUNCTION LIST](https://upstash.com/docs/redis/sdks/ts/commands/functions/list.md): List details about the registered libraries and functions.
- [FUNCTION LOAD](https://upstash.com/docs/redis/sdks/ts/commands/functions/load.md): Load a library to Redis.
- [FUNCTION STATS](https://upstash.com/docs/redis/sdks/ts/commands/functions/stats.md): Return information about the function running engine.
- [DEL](https://upstash.com/docs/redis/sdks/ts/commands/generic/del.md): Removes the specified keys. A key is ignored if it does not exist.
- [EXISTS](https://upstash.com/docs/redis/sdks/ts/commands/generic/exists.md): Check if a key exists.
- [EXPIRE](https://upstash.com/docs/redis/sdks/ts/commands/generic/expire.md): Sets a timeout on key. The key will automatically be deleted.
- [EXPIREAT](https://upstash.com/docs/redis/sdks/ts/commands/generic/expireat.md): Sets a timeout on key. The key will automatically be deleted.
- [KEYS](https://upstash.com/docs/redis/sdks/ts/commands/generic/keys.md): Returns all keys matching pattern.
- [PERSIST](https://upstash.com/docs/redis/sdks/ts/commands/generic/persist.md): Remove any timeout set on the key.
- [PEXPIRE](https://upstash.com/docs/redis/sdks/ts/commands/generic/pexpire.md): Sets a timeout on key. After the timeout has expired, the key will automatically be deleted.
- [PEXPIREAT](https://upstash.com/docs/redis/sdks/ts/commands/generic/pexpireat.md): Sets a timeout on key. After the timeout has expired, the key will automatically be deleted.
- [PTTL](https://upstash.com/docs/redis/sdks/ts/commands/generic/pttl.md): Return the expiration in milliseconds of a key.
- [RANDOMKEY](https://upstash.com/docs/redis/sdks/ts/commands/generic/randomkey.md): Returns a random key from database
- [RENAME](https://upstash.com/docs/redis/sdks/ts/commands/generic/rename.md): Rename a key
- [RENAMENX](https://upstash.com/docs/redis/sdks/ts/commands/generic/renamenx.md): Rename a key if it does not already exist.
- [SCAN](https://upstash.com/docs/redis/sdks/ts/commands/generic/scan.md): Scan the database for keys.
- [TOUCH](https://upstash.com/docs/redis/sdks/ts/commands/generic/touch.md): Alters the last access time of one or more keys
- [TTL](https://upstash.com/docs/redis/sdks/ts/commands/generic/ttl.md): Return the expiration in seconds of a key.
- [TYPE](https://upstash.com/docs/redis/sdks/ts/commands/generic/type.md): Get the type of a key.
- [UNLINK](https://upstash.com/docs/redis/sdks/ts/commands/generic/unlink.md): Removes the specified keys. A key is ignored if it does not exist.
- [HDEL](https://upstash.com/docs/redis/sdks/ts/commands/hash/hdel.md): Deletes one or more hash fields.
- [HEXISTS](https://upstash.com/docs/redis/sdks/ts/commands/hash/hexists.md): Checks if a field exists in a hash.
- [HEXPIRE](https://upstash.com/docs/redis/sdks/ts/commands/hash/hexpire.md): Sets an expiration time for one or more fields in a hash.
- [HEXPIREAT](https://upstash.com/docs/redis/sdks/ts/commands/hash/hexpireat.md): Sets an expiration time for field(s) in a hash in seconds since the Unix epoch.
- [HEXPIRETIME](https://upstash.com/docs/redis/sdks/ts/commands/hash/hexpiretime.md): Retrieves the expiration time of field(s) in a hash in seconds.
- [HGET](https://upstash.com/docs/redis/sdks/ts/commands/hash/hget.md): Retrieves the value of a hash field.
- [HGETALL](https://upstash.com/docs/redis/sdks/ts/commands/hash/hgetall.md): Retrieves all fields from a hash.
- [HGETDEL](https://upstash.com/docs/redis/sdks/ts/commands/hash/hgetdel.md): Get and delete hash fields atomically.
- [HGETEX](https://upstash.com/docs/redis/sdks/ts/commands/hash/hgetex.md): Get hash fields with expiration support.
- [HINCRBY](https://upstash.com/docs/redis/sdks/ts/commands/hash/hincrby.md): Increments the value of a hash field by a given amount
- [HINCRBYFLOAT](https://upstash.com/docs/redis/sdks/ts/commands/hash/hincrbyfloat.md): Increments the value of a hash field by a given float value.
- [HKEYS](https://upstash.com/docs/redis/sdks/ts/commands/hash/hkeys.md): Return all field names in the hash stored at key.
- [HLEN](https://upstash.com/docs/redis/sdks/ts/commands/hash/hlen.md): Returns the number of fields contained in the hash stored at key.
- [HMGET](https://upstash.com/docs/redis/sdks/ts/commands/hash/hmget.md): Return the requested fields and their values.
- [HPERSIST](https://upstash.com/docs/redis/sdks/ts/commands/hash/hpersist.md): Remove the expiration from one or more fields in a hash.
- [HPEXPIRE](https://upstash.com/docs/redis/sdks/ts/commands/hash/hpexpire.md): Sets an expiration time for a field in a hash in milliseconds.
- [HPEXPIREAT](https://upstash.com/docs/redis/sdks/ts/commands/hash/hpexpireat.md): Sets an expiration time for field(s) in a hash in milliseconds since the Unix epoch.
- [HPEXPIRETIME](https://upstash.com/docs/redis/sdks/ts/commands/hash/hpexpiretime.md): Retrieves the expiration time of a field in a hash in milliseconds.
- [HPTTL](https://upstash.com/docs/redis/sdks/ts/commands/hash/hpttl.md): Retrieves the remaining time-to-live (TTL) for field(s) in a hash in milliseconds.
- [HRANDFIELD](https://upstash.com/docs/redis/sdks/ts/commands/hash/hrandfield.md): Return a random field from a hash
- [HSCAN](https://upstash.com/docs/redis/sdks/ts/commands/hash/hscan.md): Scan a hash for fields.
- [HSET](https://upstash.com/docs/redis/sdks/ts/commands/hash/hset.md): Write one or more fields to a hash.
- [HSETEX](https://upstash.com/docs/redis/sdks/ts/commands/hash/hsetex.md): Set hash fields with expiration support.
- [HSETNX](https://upstash.com/docs/redis/sdks/ts/commands/hash/hsetnx.md): Write a field to a hash but only if the field does not exist.
- [HSTRLEN](https://upstash.com/docs/redis/sdks/ts/commands/hash/hstrlen.md): Returns the string length of a value in a hash.
- [HTTL](https://upstash.com/docs/redis/sdks/ts/commands/hash/httl.md): Retrieves the remaining time-to-live (TTL) for field(s) in a hash in seconds.
- [HVALS](https://upstash.com/docs/redis/sdks/ts/commands/hash/hvals.md): Returns all values in the hash stored at key.
- [JSON.ARRAPPEND](https://upstash.com/docs/redis/sdks/ts/commands/json/arrappend.md): Append values to the array at path in the JSON document at key.
- [JSON.ARRINDEX](https://upstash.com/docs/redis/sdks/ts/commands/json/arrindex.md): Search for the first occurrence of a JSON value in an array.
- [JSON.ARRINSERT](https://upstash.com/docs/redis/sdks/ts/commands/json/arrinsert.md): Insert the json values into the array at path before the index (shifts to the right).
- [JSON.ARRLEN](https://upstash.com/docs/redis/sdks/ts/commands/json/arrlen.md): Report the length of the JSON array at `path` in `key`.
- [JSON.ARRPOP](https://upstash.com/docs/redis/sdks/ts/commands/json/arrpop.md): Remove and return an element from the index in the array. By default the last element from an array is popped.
- [JSON.ARRTRIM](https://upstash.com/docs/redis/sdks/ts/commands/json/arrtrim.md): Trim an array so that it contains only the specified inclusive range of elements.
- [JSON.CLEAR](https://upstash.com/docs/redis/sdks/ts/commands/json/clear.md): Clear container values (arrays/objects) and set numeric values to 0.
- [JSON.DEL](https://upstash.com/docs/redis/sdks/ts/commands/json/del.md): Delete a key from a JSON document.
- [JSON.FORGET](https://upstash.com/docs/redis/sdks/ts/commands/json/forget.md): Delete a key from a JSON document.
- [JSON.GET](https://upstash.com/docs/redis/sdks/ts/commands/json/get.md): Get a single value from a JSON document.
- [JSON.MERGE](https://upstash.com/docs/redis/sdks/ts/commands/json/merge.md): Merges the JSON value at path in key with the provided value.
- [JSON.MGET](https://upstash.com/docs/redis/sdks/ts/commands/json/mget.md): Get the same path from multiple JSON documents.
- [JSON.MSET](https://upstash.com/docs/redis/sdks/ts/commands/json/mset.md): Sets multiple JSON values at multiple paths in multiple keys.
- [JSON.NUMINCRBY](https://upstash.com/docs/redis/sdks/ts/commands/json/numincrby.md): Increment the number value stored at `path` by number.
- [JSON.NUMMULTBY](https://upstash.com/docs/redis/sdks/ts/commands/json/nummultby.md): Multiply the number value stored at `path` by number.
- [JSON.OBJKEYS](https://upstash.com/docs/redis/sdks/ts/commands/json/objkeys.md): Return the keys in the object that`s referenced by path.
- [JSON.OBJLEN](https://upstash.com/docs/redis/sdks/ts/commands/json/objlen.md): Report the number of keys in the JSON object at `path` in `key`.
- [JSON.SET](https://upstash.com/docs/redis/sdks/ts/commands/json/set.md): Set the JSON value at path in key.
- [JSON.STRAPPEND](https://upstash.com/docs/redis/sdks/ts/commands/json/strappend.md): Append the json-string values to the string at path.
- [JSON.STRLEN](https://upstash.com/docs/redis/sdks/ts/commands/json/strlen.md): Report the length of the JSON String at path in key
- [JSON.TOGGLE](https://upstash.com/docs/redis/sdks/ts/commands/json/toggle.md): Toggle a boolean value stored at `path`.
- [JSON.TYPE](https://upstash.com/docs/redis/sdks/ts/commands/json/type.md): Report the type of JSON value at `path`.
- [LINDEX](https://upstash.com/docs/redis/sdks/ts/commands/list/lindex.md): Returns the element at index index in the list stored at key.
- [LINSERT](https://upstash.com/docs/redis/sdks/ts/commands/list/linsert.md): Insert an element before or after another element in a list
- [LLEN](https://upstash.com/docs/redis/sdks/ts/commands/list/llen.md): Returns the length of the list stored at key.
- [LMOVE](https://upstash.com/docs/redis/sdks/ts/commands/list/lmove.md): Move an element from one list to another.
- [LPOP](https://upstash.com/docs/redis/sdks/ts/commands/list/lpop.md): Remove and return the first element(s) of a list
- [LPOS](https://upstash.com/docs/redis/sdks/ts/commands/list/lpos.md): Returns the index of matching elements inside a list.
- [LPUSH](https://upstash.com/docs/redis/sdks/ts/commands/list/lpush.md): Push an element at the head of the list.
- [LPUSHX](https://upstash.com/docs/redis/sdks/ts/commands/list/lpushx.md): Push an element at the head of the list only if the list exists.
- [LRANGE](https://upstash.com/docs/redis/sdks/ts/commands/list/lrange.md): Returns the specified elements of the list stored at key.
- [LREM](https://upstash.com/docs/redis/sdks/ts/commands/list/lrem.md): Remove the first `count` occurrences of an element from a list.
- [LSET](https://upstash.com/docs/redis/sdks/ts/commands/list/lset.md): Set a value at a specific index.
- [LTRIM](https://upstash.com/docs/redis/sdks/ts/commands/list/ltrim.md): Trim a list to the specified range
- [RPOP](https://upstash.com/docs/redis/sdks/ts/commands/list/rpop.md): Remove and return the last element(s) of a list
- [RPUSH](https://upstash.com/docs/redis/sdks/ts/commands/list/rpush.md): Push an element at the end of the list.
- [RPUSHX](https://upstash.com/docs/redis/sdks/ts/commands/list/rpushx.md): Push an element at the end of the list only if the list exists.
- [Overview](https://upstash.com/docs/redis/sdks/ts/commands/overview.md): Available Commands in @upstash/redis
- [PSUBSCRIBE](https://upstash.com/docs/redis/sdks/ts/commands/pubsub/psubscribe.md): Subscribe to a channel by patterns/wildcards
- [PUBLISH](https://upstash.com/docs/redis/sdks/ts/commands/pubsub/publish.md): Publish a message to a channel
- [SUBSCRIBE](https://upstash.com/docs/redis/sdks/ts/commands/pubsub/subscribe.md): Subscribe to a channel
- [EVAL](https://upstash.com/docs/redis/sdks/ts/commands/scripts/eval.md): Evaluate a Lua script server side.
- [EVAL_RO](https://upstash.com/docs/redis/sdks/ts/commands/scripts/eval_ro.md): Evaluate a read-only Lua script server side.
- [EVALSHA](https://upstash.com/docs/redis/sdks/ts/commands/scripts/evalsha.md): Evaluate a cached Lua script server side.
- [EVALSHA_RO](https://upstash.com/docs/redis/sdks/ts/commands/scripts/evalsha_ro.md): Evaluate a cached read-only Lua script server side.
- [SCRIPT EXISTS](https://upstash.com/docs/redis/sdks/ts/commands/scripts/script_exists.md): Check if scripts exist in the script cache.
- [SCRIPT FLUSH](https://upstash.com/docs/redis/sdks/ts/commands/scripts/script_flush.md): Removes all scripts from the script cache.
- [SCRIPT LOAD](https://upstash.com/docs/redis/sdks/ts/commands/scripts/script_load.md): Load the specified Lua script into the script cache.
- [DBSIZE](https://upstash.com/docs/redis/sdks/ts/commands/server/dbsize.md): Count the number of keys in the database.
- [FLUSHALL](https://upstash.com/docs/redis/sdks/ts/commands/server/flushall.md)
- [FLUSHDB](https://upstash.com/docs/redis/sdks/ts/commands/server/flushdb.md)
- [SADD](https://upstash.com/docs/redis/sdks/ts/commands/set/sadd.md): Adds one or more members to a set.
- [SCARD](https://upstash.com/docs/redis/sdks/ts/commands/set/scard.md): Return how many members are in a set
- [SDIFF](https://upstash.com/docs/redis/sdks/ts/commands/set/sdiff.md): Return the difference between sets
- [SDIFFSTORE](https://upstash.com/docs/redis/sdks/ts/commands/set/sdiffstore.md): Write the difference between sets to a new set
- [SINTER](https://upstash.com/docs/redis/sdks/ts/commands/set/sinter.md): Return the intersection between sets
- [SINTERSTORE](https://upstash.com/docs/redis/sdks/ts/commands/set/sinterstore.md): Return the intersection between sets and store the resulting set in a key
- [SISMEMBER](https://upstash.com/docs/redis/sdks/ts/commands/set/sismember.md): Check if a member exists in a set
- [SMEMBERS](https://upstash.com/docs/redis/sdks/ts/commands/set/smembers.md): Return all the members of a set
- [SMISMEMBER](https://upstash.com/docs/redis/sdks/ts/commands/set/smismember.md): Check if multiple members exist in a set
- [SMOVE](https://upstash.com/docs/redis/sdks/ts/commands/set/smove.md): Move a member from one set to another
- [SPOP](https://upstash.com/docs/redis/sdks/ts/commands/set/spop.md): Removes and returns one or more random members from a set.
- [SRANDMEMBER](https://upstash.com/docs/redis/sdks/ts/commands/set/srandmember.md): Returns one or more random members from a set.
- [SREM](https://upstash.com/docs/redis/sdks/ts/commands/set/srem.md): Remove one or more members from a set
- [SSCAN](https://upstash.com/docs/redis/sdks/ts/commands/set/sscan.md): Scan a set
- [SUNION](https://upstash.com/docs/redis/sdks/ts/commands/set/sunion.md): Return the union between sets
- [SUNIONSTORE](https://upstash.com/docs/redis/sdks/ts/commands/set/sunionstore.md): Return the union between sets and store the resulting set in a key
- [XACK](https://upstash.com/docs/redis/sdks/ts/commands/stream/xack.md): Removes one or multiple messages from the pending entries list of a stream consumer group.
- [XACKDEL](https://upstash.com/docs/redis/sdks/ts/commands/stream/xackdel.md): Acknowledge and delete stream entries atomically.
- [XADD](https://upstash.com/docs/redis/sdks/ts/commands/stream/xadd.md): Appends one or more new entries to a stream.
- [XAUTOCLAIM](https://upstash.com/docs/redis/sdks/ts/commands/stream/xautoclaim.md): Changes the ownership of pending messages from one consumer to another in a stream consumer group.
- [XCLAIM](https://upstash.com/docs/redis/sdks/ts/commands/stream/xclaim.md): Changes the ownership of pending messages from one consumer to another in a stream consumer group.
- [XDEL](https://upstash.com/docs/redis/sdks/ts/commands/stream/xdel.md): Removes the specified entries from a stream, and returns the number of entries deleted.
- [XDELEX](https://upstash.com/docs/redis/sdks/ts/commands/stream/xdelex.md): Extended delete for streams with reference control.
- [XGROUP](https://upstash.com/docs/redis/sdks/ts/commands/stream/xgroup.md): Manage consumer groups for Redis streams.
- [XINFO](https://upstash.com/docs/redis/sdks/ts/commands/stream/xinfo.md): Returns information about streams, consumer groups, and consumers.
- [XLEN](https://upstash.com/docs/redis/sdks/ts/commands/stream/xlen.md): Returns the number of entries inside a stream.
- [XPENDING](https://upstash.com/docs/redis/sdks/ts/commands/stream/xpending.md): Returns information about pending messages in a stream consumer group.
- [XRANGE](https://upstash.com/docs/redis/sdks/ts/commands/stream/xrange.md): Returns stream entries matching a given range of IDs.
- [XREAD](https://upstash.com/docs/redis/sdks/ts/commands/stream/xread.md): Reads data from one or multiple streams, starting from the specified IDs.
- [XREADGROUP](https://upstash.com/docs/redis/sdks/ts/commands/stream/xreadgroup.md): Reads data from a stream as part of a consumer group.
- [XREVRANGE](https://upstash.com/docs/redis/sdks/ts/commands/stream/xrevrange.md): Returns stream entries matching a given range of IDs in reverse order.
- [XTRIM](https://upstash.com/docs/redis/sdks/ts/commands/stream/xtrim.md): Trims the stream by removing entries to keep it at a reasonable size.
- [String Commands](https://upstash.com/docs/redis/sdks/ts/commands/string.md)
- [APPEND](https://upstash.com/docs/redis/sdks/ts/commands/string/append.md): Append a value to a string stored at key.
- [DECR](https://upstash.com/docs/redis/sdks/ts/commands/string/decr.md): Decrement the integer value of a key by one
- [DECRBY](https://upstash.com/docs/redis/sdks/ts/commands/string/decrby.md): Decrement the integer value of a key by a given number.
- [GET](https://upstash.com/docs/redis/sdks/ts/commands/string/get.md): Return the value of the specified key or `null` if the key doesn't exist.
- [GETDEL](https://upstash.com/docs/redis/sdks/ts/commands/string/getdel.md): Return the value of the specified key and delete the key.
- [GETRANGE](https://upstash.com/docs/redis/sdks/ts/commands/string/getrange.md): Return a substring of value at the specified key.
- [GETSET](https://upstash.com/docs/redis/sdks/ts/commands/string/getset.md): Return the value of the specified key and replace it with a new value.
- [INCR](https://upstash.com/docs/redis/sdks/ts/commands/string/incr.md): Increment the integer value of a key by one
- [INCRBY](https://upstash.com/docs/redis/sdks/ts/commands/string/incrby.md): Increment the integer value of a key by a given number.
- [INCRBYFLOAT](https://upstash.com/docs/redis/sdks/ts/commands/string/incrbyfloat.md): Increment the float value of a key by a given number.
- [MGET](https://upstash.com/docs/redis/sdks/ts/commands/string/mget.md): Load multiple keys from Redis in one go.
- [MSET](https://upstash.com/docs/redis/sdks/ts/commands/string/mset.md): Set multiple keys in one go.
- [MSETNX](https://upstash.com/docs/redis/sdks/ts/commands/string/msetnx.md): Set multiple keys in one go unless they exist already.
- [SET](https://upstash.com/docs/redis/sdks/ts/commands/string/set.md): Set a key to hold a string value.
- [SETRANGE](https://upstash.com/docs/redis/sdks/ts/commands/string/setrange.md): Writes the value of key at offset.
- [STRLEN](https://upstash.com/docs/redis/sdks/ts/commands/string/strlen.md): Return the length of a string stored at a key.
- [Transactions](https://upstash.com/docs/redis/sdks/ts/commands/transaction.md): Transactions
- [ZADD](https://upstash.com/docs/redis/sdks/ts/commands/zset/zadd.md): Add a member to a sorted set, or update its score if it already exists.
- [ZCARD](https://upstash.com/docs/redis/sdks/ts/commands/zset/zcard.md): Returns the number of elements in the sorted set stored at key.
- [ZCOUNT](https://upstash.com/docs/redis/sdks/ts/commands/zset/zcount.md): Returns the number of elements in the sorted set stored at key filterd by score.
- [ZDIFFSTORE](https://upstash.com/docs/redis/sdks/ts/commands/zset/zdiffstore.md): Writes the difference between sets to a new key.
- [ZINCRBY](https://upstash.com/docs/redis/sdks/ts/commands/zset/zincrby.md): Increment the score of a member.
- [ZINTERSTORE](https://upstash.com/docs/redis/sdks/ts/commands/zset/zinterstore.md): Writes the intersection between sets to a new key.
- [ZLEXCOUNT](https://upstash.com/docs/redis/sdks/ts/commands/zset/zlexcount.md): Returns the number of elements in the sorted set stored at key filtered by lex.
- [ZMSCORE](https://upstash.com/docs/redis/sdks/ts/commands/zset/zmscore.md): Returns the scores of multiple members.
- [ZPOPMAX](https://upstash.com/docs/redis/sdks/ts/commands/zset/zpopmax.md): Removes and returns up to count members with the highest scores in the sorted set stored at key.
- [ZPOPMIN](https://upstash.com/docs/redis/sdks/ts/commands/zset/zpopmin.md): Removes and returns up to count members with the lowest scores in the sorted set stored at key.
- [ZRANGE](https://upstash.com/docs/redis/sdks/ts/commands/zset/zrange.md): Returns the specified range of elements in the sorted set stored at key.
- [ZRANK](https://upstash.com/docs/redis/sdks/ts/commands/zset/zrank.md): Returns the rank of a member
- [ZREM](https://upstash.com/docs/redis/sdks/ts/commands/zset/zrem.md): Remove one or more members from a sorted set
- [ZREMRANGEBYLEX](https://upstash.com/docs/redis/sdks/ts/commands/zset/zremrangebylex.md): Remove all members in a sorted set between the given lexicographical range.
- [ZREMRANGEBYRANK](https://upstash.com/docs/redis/sdks/ts/commands/zset/zremrangebyrank.md): Remove all members in a sorted set between the given ranks.
- [ZREMRANGEBYSCORE](https://upstash.com/docs/redis/sdks/ts/commands/zset/zremrangebyscore.md): Remove all members in a sorted set between the given scores.
- [ZREVRANK](https://upstash.com/docs/redis/sdks/ts/commands/zset/zrevrank.md): Returns the rank of a member in a sorted set, with scores ordered from high to low.
- [ZSCAN](https://upstash.com/docs/redis/sdks/ts/commands/zset/zscan.md): Scan a sorted set
- [ZSCORE](https://upstash.com/docs/redis/sdks/ts/commands/zset/zscore.md): Returns the scores of a member.
- [ZUNIONSTORE](https://upstash.com/docs/redis/sdks/ts/commands/zset/zunionstore.md): Writes the union between sets to a new key.

# Deployment
Source: https://upstash.com/docs/redis/sdks/ts/deployment

We support various platforms, such as nodejs, cloudflare and fastly. Platforms
differ slightly when it comes to environment variables and their `fetch` api.
Please use the correct import when deploying to special platforms.

## Node.js / Browser

Examples: Vercel, Netlify, AWS Lambda

If you are running on nodejs you can set `UPSTASH_REDIS_REST_URL` and
`UPSTASH_REDIS_REST_TOKEN` as environment variable and create a redis instance
like this:

```ts
import { Redis } from "@upstash/redis"

const redis = new Redis({
  url: <UPSTASH_REDIS_REST_URL>,
  token: <UPSTASH_REDIS_REST_TOKEN>,
})

// or load directly from env
const redis = Redis.fromEnv()
```

<Info>
If you are running on nodejs v17 and earlier, `fetch` will not be natively
supported. Platforms like Vercel, Netlify, Deno, Fastly etc. provide a polyfill
for you. But if you are running on bare node, you need to either specify a
polyfill yourself or change the import path slightly:

```typescript
import { Redis } from "@upstash/redis/with-fetch";
```

</Info>

* [Code example](https://github.com/upstash/upstash-redis/blob/main/examples/nodejs)

## Cloudflare Workers

Cloudflare handles environment variables differently than Node.js. Please add
`UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` using
`wrangler secret put ...` or in the cloudflare dashboard.

Afterwards you can create a redis instance:

```ts
import { Redis } from "@upstash/redis/cloudflare"

const redis = new Redis({
  url: <UPSTASH_REDIS_REST_URL>,
  token: <UPSTASH_REDIS_REST_TOKEN>,
})

// or load directly from global env

// service worker
const redis = Redis.fromEnv()

// module worker
export default {
  async fetch(request: Request, env: Bindings) {
    const redis = Redis.fromEnv(env)
    // ...
  }
}
```

* [Code example](https://github.com/upstash/upstash-redis/tree/main/examples/cloudflare-workers)
* [Code example typescript](https://github.com/upstash/upstash-redis/tree/main/examples/cloudflare-workers-with-typescript)
* [Code example Wrangler 1](https://github.com/upstash/upstash-redis/tree/main/examples/cloudflare-workers-with-wrangler-1)
* [Documentation](https://docs.upstash.com/redis/tutorials/cloudflare_workers_with_redis)

## Fastly

Fastly introduces a concept called
[backend](https://developer.fastly.com/reference/api/services/backend/). You
need to configure a backend in your `fastly.toml`. An example can be found
[here](https://github.com/upstash/upstash-redis/blob/main/examples/fastly/fastly.toml).
Until the fastly api stabilizes we recommend creating an instance manually:

```ts
import { Redis } from "@upstash/redis/fastly"

const redis = new Redis({
  url: <UPSTASH_REDIS_REST_URL>,
  token: <UPSTASH_REDIS_REST_TOKEN>,
  backend: <BACKEND_NAME>,
})
```

* [Code example](https://github.com/upstash/upstash-redis/tree/main/examples/fastly)
* [Documentation](https://blog.upstash.com/fastly-compute-edge-with-redis)

## Deno

Examples: [Deno Deploy](https://deno.com/deploy),
[Netlify Edge](https://www.netlify.com/products/edge/)

```ts
import { Redis } from "npm:@upstash/redis"

const redis = new Redis({
  url: <UPSTASH_REDIS_REST_URL>,
  token: <UPSTASH_REDIS_REST_TOKEN>,
})

// or
const redis = Redis.fromEnv();
```

# Developing or Testing
Source: https://upstash.com/docs/redis/sdks/ts/developing

When developing or testing your application, you might not want or can not use
Upstash over the internet. In this case, you can use a community project called
[Serverless Redis HTTP (SRH)](https://github.com/hiett/serverless-redis-http)
created by [Scott Hiett](https://x.com/hiettdigital).

SRH is a Redis proxy and connection pooler that uses HTTP rather than the Redis
binary protocol. The aim of this project is to be entirely compatible with
Upstash, and work with any Upstash supported Redis version.

We are working with Scott together to keep SRH up to date with the latest
Upstash features.

## Use cases for SRH:

* For usage in your CI pipelines, creating Upstash databases is tedious, or you
  have lots of parallel runs.
  * See [Using in GitHub Actions](#in-github-actions) on how to quickly get SRH
    setup for this context.
* For usage inside of Kubernetes, or any network whereby the Redis server is not
  exposed to the internet.
  * See [Using in Docker Compose](#via-docker-compose) for the various setup
    options directly using the Docker Container.
* For local development environments, where you have a local Redis server
  running, or require offline access.
  * See [Using the Docker Command](#via-docker-command), or
    [Using Docker Compose](#via-docker-compose).

## Setting up SRH

### Via Docker command

If you have a locally running Redis server, you can simply start an SRH
container that connects to it. In this example, SRH will be running on port
`8080`.

```bash
docker run \
    -it -d -p 8080:80 --name srh \
    -e SRH_MODE=env \
    -e SRH_TOKEN=your_token_here \
    -e SRH_CONNECTION_STRING="redis://your_server_here:6379" \
    hiett/serverless-redis-http:latest
```

### Via Docker Compose

If you wish to run in Kubernetes, this should contain all the basics would need
to set that up. However, be sure to read the Configuration Options, because you
can create a setup whereby multiple Redis servers are proxied.

```yml
version: "3"
services:
  redis:
    image: redis
    ports:
      - "6379:6379"
  serverless-redis-http:
    ports:
      - "8079:80"
    image: hiett/serverless-redis-http:latest
    environment:
      SRH_MODE: env
      SRH_TOKEN: example_token
      SRH_CONNECTION_STRING: "redis://redis:6379" # Using `redis` hostname since they're in the same Docker network.
```

### In GitHub Actions

SRH works nicely in GitHub Actions because you can run it as a container in a
job's services. Simply start a Redis server, and then SRH alongside it. You
don't need to worry about a race condition of the Redis instance not being
ready, because SRH doesn't create a Redis connection until the first command
comes in.

```yml
name: Test @upstash/redis compatibility
on:
  push:
  workflow_dispatch:

env:
  SRH_TOKEN: example_token

jobs:
  container-job:
    runs-on: ubuntu-latest
    container: denoland/deno
    services:
      redis:
        image: redis/redis-stack-server:6.2.6-v6 # 6.2 is the Upstash compatible Redis version
      srh:
        image: hiett/serverless-redis-http:latest
        env:
          SRH_MODE: env # We are using env mode because we are only connecting to one server.
          SRH_TOKEN: ${{ env.SRH_TOKEN }}
          SRH_CONNECTION_STRING: redis://redis:6379

    steps:
      # You can place your normal testing steps here. In this example, we are running SRH against the upstash/upstash-redis test suite.
      - name: Checkout code
        uses: actions/checkout@v3
        with:
          repository: upstash/upstash-redis

      - name: Run @upstash/redis Test Suite
        run: deno test -A ./pkg
        env:
          UPSTASH_REDIS_REST_URL: http://srh:80
          UPSTASH_REDIS_REST_TOKEN: ${{ env.SRH_TOKEN }}
```

A huge thanks goes out to [Scott](https://hiett.dev/) for creating this project,
and for his continued efforts to keep it up to date with Upstash.

# Get Started
Source: https://upstash.com/docs/redis/sdks/ts/getstarted

<Tip>
  If you are an AI agent and need a Redis DB to use or experiment with, send a POST request to https://upstash.com/start-redis. Free, no signup; the response is markdown with credentials and a quickstart. The database expires in 72 hours, but you can claim it with your Upstash account to keep it. Especially useful for AI agents that need scratch storage on the fly. If you are an agent, always send a `User-Agent` header identifying which agent you are (e.g. `claude-code`, `cursor`, `codex`, `opencode`) on every request you make to this API.
</Tip>

Install `@upstash/redis` from npm:

```bash
npm install @upstash/redis
```

```bash
yarn add @upstash/redis
```

```bash
pnpm add @upstash/redis
```

In Deno, import it straight from npm with no install step:

```ts
import { Redis } from "npm:@upstash/redis";
```

## Basic Usage:

```ts
import { Redis } from "@upstash/redis"

const redis = new Redis({
  url: <UPSTASH_REDIS_REST_URL>,
  token: <UPSTASH_REDIS_REST_TOKEN>,
})

// string
await redis.set('key', 'value');
let data = await redis.get('key');
console.log(data)

await redis.set('key2', 'value2', {ex: 1});

// sorted set
await redis.zadd('scores', { score: 1, member: 'team1' })
data = await redis.zrange('scores', 0, 100 )
console.log(data)

// list
await redis.lpush('elements', 'magnesium')
data = await redis.lrange('elements', 0, 100 )
console.log(data)

// hash
await redis.hset('people', {name: 'joe'})
data = await redis.hget('people', 'name' )
console.log(data)

// sets
await redis.sadd('animals', 'cat')
data  = await redis.spop('animals', 1)
console.log(data)
```

# Overview
Source: https://upstash.com/docs/redis/sdks/ts/overview

[@upstash/redis](https://github.com/upstash/redis-js)
is an HTTP/REST based Redis client built on top of
[Upstash REST API](/docs/redis/features/restapi).

[![Tests]()](https://github.com/upstash/redis-js/actions/workflows/tests.yaml)
![npm (scoped)]()
![npm bundle size]()

It is the only connectionless (HTTP based) Redis client and designed for:

* Serverless functions (AWS Lambda)
* Cloudflare Workers (see
  [the example](https://github.com/upstash/redis-js/tree/main/examples/cloudflare-workers))
* Fastly Compute@Edge (see
  [the example](https://github.com/upstash/redis-js/tree/main/examples/fastly))
* Next.js (see [the quickstart](/docs/redis/quickstarts/nextjs-app-router)), Jamstack
* Client side web/mobile applications
* WebAssembly
* and other environments where HTTP is preferred over TCP.

See
[the list of APIs](/docs/redis/features/restapi#rest-redis-api-compatibility)
supported by the Upstash REST API. For typed `@upstash/redis` command helpers,
see the [TypeScript SDK command reference](/docs/redis/sdks/ts/commands/overview).

# Auto-Pipelining
Source: https://upstash.com/docs/redis/sdks/ts/pipelining/auto-pipeline

### Auto Pipelining

Auto pipelining allows you to use the Redis client as usual
while in the background it tries to send requests in batches
whenever possible.

In a nutshell, the client will accumulate commands in a pipeline
and wait for a short amount of time for more commands to arrive.
When there are no more commands, it will execute them as a batch.

This feature is enabled by default. To disable it, simply pass `enableAutoPipelining: false`
when creating the Redis client:

<CodeGroup>

```ts Redis
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv({
  enableAutoPipelining: false
});
```

```ts fromEnv
import { Redis } from "@upstash/redis";

const redis = new Redis({
  url: <UPSTASH_REDIS_REST_URL>,
  token: <UPSTASH_REDIS_REST_TOKEN>,
  enableAutoPipelining: true
})
```
</CodeGroup>

This is especially useful in cases when we want to make async
requests or when we want to make requests in batches.

```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv({
  latencyLogging: false,
  enableAutoPipelining: true
});

// async call to redis. Not executed right away, instead
// added to the pipeline
redis.hincrby("Brooklyn", "visited", 1);

// making requests in batches
const brooklynInfo = Promise.all([
  redis.hget("Brooklyn", "coordinates"),
  redis.hget("Brooklyn", "population")
]);

// when we call await, the three commands are executed
// as a pipeline automatically. A single PIPELINE command
// is executed instead of three requests and the results
// are returned:
const [ coordinates, population ] = await brooklynInfo;
```

The benefit of auto pipelining is that it reduces the number
of HTTP requests made like pipelining and transaction while
being extremely simple to enable and use. It's especially
useful in cases like Vercel Edge and [Cloudflare Workers, where the number of
simultaneous requests is limited by 6](https://developers.cloudflare.com/workers/platform/limits/#account-plan-limits).

To learn more about how auto pipelining can be utilized in a
project, see
[the auto-pipeline example project under `upstash-redis` repository](https://github.com/upstash/upstash-redis/tree/main/examples/auto-pipeline)

### How it Works

For auto pipeline to work, the client keeps an active pipeline
and adds incoming commands to this pipeline. After the command
is added to the pipeline, execution of the pipeline is delayed
by releasing the control of the Node thread.

The pipeline executes when one of these two conditions are met:
No more commands are being added or at least one of the commands
added is being 'awaited'.

This means that if you are awaiting every time you run a command,
you won't benefit much from auto pipelining since each await will
trigger a pipeline:

```ts
const foo = await redis.get("foo") // makes a PIPELINE call
const bar = await redis.get("bar") // makes another PIPELINE call
```

In these cases, we suggest using `Promise.all`:

```ts
// makes a single PIPELINE call:
const [ foo, bar ] = await Promise.all([
  redis.get("foo"),
  redis.get("bar")
])
```

In addition to resulting in a single PIPELINE call, the commands
in `Promise.all` are executed in the order they are written!

# Pipeline & Transaction
Source: https://upstash.com/docs/redis/sdks/ts/pipelining/pipeline-transaction

### Pipeline

Pipelining commands allows you to send a single http request with multiple
commands. Keep in mind, that the execution of pipelines is not atomic and the
execution of other commands can interleave.

```ts
import { Redis } from "@upstash/redis";

const redis = new Redis({
  /* auth */
});

const p = redis.pipeline();

// Now you can chain multiple commands to create your pipeline:

p.set("key", 2);
p.incr("key");

// or inline:
p.hset("key2", "field", { hello: "world" }).hvals("key2");

// Execute the pipeline once you are done building it:
// `exec` returns an array where each element represents the response of a command in the pipeline.
// You can optionally provide a type like this to get a typed response.
const res = await p.exec<[Type1, Type2, Type3]>();
```

For more information about pipelines using REST see
[here](https://blog.upstash.com/pipeline).

If you wish to benefit from pipeline automatically,
you can simply enable auto-pipelining to make your redis client
handle the commands in batches in the background. See
[the Auto-pipelining page](https://docs.upstash.com/redis/sdks/ts/pipelining/auto-pipeline).

### Transaction

Remember that the pipeline is able to send multiple commands at once but
can't execute them atomically. With transactions, you can make the commands
execute atomically.

```ts
import { Redis } from "@upstash/redis";

const redis = new Redis({
  /* auth */
});

const p = redis.multi();

p.set("key", 2);
p.incr("key");

// or inline:
p.hset("key2", "field", { hello: "world" }).hvals("key2");

// execute the transaction
const res = await p.exec<[Type1, Type2, Type3]>();
```

# Retries
Source: https://upstash.com/docs/redis/sdks/ts/retries

By default `@upstash/redis` will retry sending you request when network errors
occur. It will retry 5 times with a backoff of
`(retryCount) => Math.exp(retryCount) * 50` milliseconds.

You can customize this in the `Redis` constructor:

```ts
new Redis({
  url: UPSTASH_REDIS_REST_URL,
  token: UPSTASH_REDIS_REST_TOKEN,
  retry: {
    retries: 5,
    backoff: (retryCount) => Math.exp(retryCount) * 50,
  },
});
```

The exact type definition can be found
[here](https://github.com/upstash/upstash-redis/blob/4948b049e0d580d1de0a4cbfeac5565d7e035cc4/pkg/http.ts#LL31C1-L49C5).

# Troubleshooting
Source: https://upstash.com/docs/redis/sdks/ts/troubleshooting

## ReferenceError: fetch is not defined

#### Problem

If you are running on nodejs v17 and earlier, fetch will not be natively
supported. Platforms like Vercel, Netlify, Deno, Fastly etc. provide a polyfill
for you. But if you are running on bare node, you need to add a polyfill.

#### Solution

```bash
npm i isomorphic-fetch
```

```ts
import { Redis } from "@upstash/redis";
import "isomorphic-fetch";

const redis = new Redis({
  /*...*/
});
```

## Hashed Response

The response from a server is not what you expect but looks like a hash?

```ts
await redis.set("key", "value");
const data = await redis.get("key");
console.log(data);

// dmFsdWU=
```

#### Problem

By default `@upstash/redis` will request responses from the server to be base64
encoded. This is to prevent issues with some edge cases when storing data where
the http response fails to be deserialized using `res.json()`

This solves the problem for almost all edge cases, but it can cause new issues.

#### Solution

You can disable this behavior by setting `responseEncoding` to `false` in the
options.

```ts
const redis = new Redis({
  // ...
  responseEncoding: false,
});
```

This should no longer be necessary, but if you are still experiencing issues
with this, please let us know:

* [Discord](https://discord.gg/w9SenAtbme)
* [X](https://x.com/upstash)
* [GitHub](https://github.com/upstash/upstash-redis/issues/new)

## Large numbers are returned as string

You are trying to load a large number and it is returned as a string instead.

```ts
await redis.set("key", "101600000000150081467");
const res = await redis("get");

// "101600000000150081467"
```

#### Problem

Javascript can not handle numbers larger than `2^53 -1` safely and would return
wrong results when trying to deserialize them. In these cases the default
deserializer will return them as string instead. This might cause a mismatch
with your custom types.

#### Solution

Please be aware that this is a limitation of javascript and take special care
when handling large numbers.

# ioredis
Source: https://upstash.com/docs/redis/search/adapters/ioredis

If you're already using **ioredis** in your project, you can use Redis Search without switching to the `@upstash/redis` client. The `@upstash/search-ioredis` package wraps your existing ioredis instance and exposes the full Redis Search API.

### Installation

```bash
npm install @upstash/search-ioredis ioredis
```

### Setup

```ts
import IORedis from "ioredis";
import { createSearch, s } from "@upstash/search-ioredis";

// Create ioredis client using your Upstash Redis URL
const ioredis = new IORedis(process.env.REDIS_URL);

// Create search client
const search = createSearch(ioredis);
```

The `REDIS_URL` should be your Upstash Redis connection string in the format:

```
rediss://default:<PASSWORD>@<YOUR-DATABASE>.upstash.io:6379
```

### Create an Index

```ts
const schema = s.object({
  name: s.string(),
  description: s.string(),
  category: s.string().noTokenize(),
  price: s.number(),
  inStock: s.boolean(),
});

await search.createIndex({
  name: "products",
  dataType: "json",
  prefix: "product:",
  schema,
});
```

### Get an Index Client

Use `search.index()` to get a client for an existing index without making a Redis call:

```ts
const index = search.index({ name: "products", schema });
```

### Add Data

Add data using standard Redis commands via your ioredis client. Any key matching the index prefix is automatically indexed:

```ts
await ioredis.call("JSON.SET", "product:1", "$", JSON.stringify({
  name: "Wireless Headphones",
  description: "Premium noise-cancelling wireless headphones with 30-hour battery life",
  category: "electronics",
  price: 199.99,
  inStock: true,
}));
```

### Search Data

```ts
// Query with a filter
const results = await index.query({
  filter: { description: "wireless" },
});

// Count matching documents
const count = await index.count({
  filter: { price: { $lt: 150 } },
});
```

### Wait for Indexing

Index updates are batched for performance. Use `waitIndexing()` when you need queries to reflect recent writes:

```ts
await index.waitIndexing();
```

### Describe an Index

```ts
const description = await index.describe();
```

### Drop an Index

```ts
const result = await index.drop();
// Returns 1 (dropped) or 0 (index not found)
```

<Note>
The adapter exposes the exact same search API as `@upstash/redis`. Refer to the [Index Management](../index-management), [Querying](../querying), [Aggregations](../aggregations), and [Schema Definition](../schema-definition) pages for the full API reference.
</Note>

# node-redis
Source: https://upstash.com/docs/redis/search/adapters/node-redis

If you're already using **node-redis** in your project, you can use Redis Search without switching to the `@upstash/redis` client. The `@upstash/search-redis` package wraps your existing node-redis client and exposes the full Redis Search API.

### Installation

```bash
npm install @upstash/search-redis redis
```

### Setup

```ts
import { createClient } from "redis";
import { createSearch, s } from "@upstash/search-redis";

// Create node-redis client using your Upstash Redis URL
const client = createClient({
  url: process.env.REDIS_URL,
});
await client.connect();

// Create search client
const search = createSearch(client);
```

The `REDIS_URL` should be your Upstash Redis connection string in the format:

```
rediss://default:<PASSWORD>@<YOUR-DATABASE>.upstash.io:6379
```

### Create an Index

```ts
const schema = s.object({
  name: s.string(),
  description: s.string(),
  category: s.string().noTokenize(),
  price: s.number(),
  inStock: s.boolean(),
});

await search.createIndex({
  name: "products",
  dataType: "json",
  prefix: "product:",
  schema,
});
```

### Get an Index Client

Use `search.index()` to get a client for an existing index without making a Redis call:

```ts
const index = search.index({ name: "products", schema });
```

### Add Data

Add data using standard Redis commands via your node-redis client. Any key matching the index prefix is automatically indexed:

```ts
await client.sendCommand(["JSON.SET", "product:1", "$", JSON.stringify({
  name: "Wireless Headphones",
  description: "Premium noise-cancelling wireless headphones with 30-hour battery life",
  category: "electronics",
  price: 199.99,
  inStock: true,
})]);
```

### Search Data

```ts
// Query with a filter
const results = await index.query({
  filter: { description: "wireless" },
});

// Count matching documents
const count = await index.count({
  filter: { price: { $lt: 150 } },
});
```

### Wait for Indexing

Index updates are batched for performance. Use `waitIndexing()` when you need queries to reflect recent writes:

```ts
await index.waitIndexing();
```

### Describe an Index

```ts
const description = await index.describe();
```

### Drop an Index

```ts
const result = await index.drop();
// Returns 1 (dropped) or 0 (index not found)
```

<Note>
The adapter exposes the exact same search API as `@upstash/redis`. Refer to the [Index Management](../index-management), [Querying](../querying), [Aggregations](../aggregations), and [Schema Definition](../schema-definition) pages for the full API reference.
</Note>

# $dateHistogram
Source: https://upstash.com/docs/redis/search/aggregation-operators/bucket-aggregations/date-histogram

`$dateHistogram` groups date values into fixed time buckets.

Use it for time-series analytics like events per hour/day.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | No |
| U64/I64/F64 | No |
| DATE | Yes |
| BOOL | No |
| KEYWORD | No |
| FACET | No |

Field must be `FAST`.

### Arguments

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `field` | `string` | Yes | Date field to bucket on. |
| `fixedInterval` | `string` | Yes | Interval string such as `"1m"`, `"1h"`, `"1d"`. |
| `offset` | `string` | No | Shift bucket boundaries. |
| `minDocCount` | `number` | No | Exclude buckets with fewer docs. |
| `hardBounds` | `{ min: number, max: number }` | No | Hard clamp for bucket range. |
| `extendedBounds` | `{ min: number, max: number }` | No | Emit buckets across this range, including empty ones. |
| `keyed` | `boolean` | No | If `true`, returns buckets as an object. Default: `false`. |

For `hardBounds` and `extendedBounds`, both `min` and `max` are required.

<Tabs>

<Tab title="TypeScript">
```ts
await index.aggregate({
  aggregations: {
    by_day: {
      $dateHistogram: {
        field: "createdAt",
        fixedInterval: "1d",
      },
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.aggregate(
    aggregations={
        "by_day": {
            "$dateHistogram": {
                "field": "createdAt",
                "fixedInterval": "1d",
            }
        }
    }
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE logs '{}' '{"by_day": {"$dateHistogram": {"field": "createdAt", "fixedInterval": "1d"}}}'
```
</Tab>

</Tabs>

### Output

```json
{
  "by_day": {
    "buckets": [
      { "key": 1704067200000, "keyAsString": "2024-01-01T00:00:00Z", "docCount": 14 },
      { "key": 1704153600000, "keyAsString": "2024-01-02T00:00:00Z", "docCount": 9 }
    ]
  }
}
```

# $facet
Source: https://upstash.com/docs/redis/search/aggregation-operators/bucket-aggregations/facet

`$facet` builds a hierarchical bucket tree from FACET field paths.

Use it for category navigation and drill-down style faceting.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | No |
| U64/I64/F64 | No |
| DATE | No |
| BOOL | No |
| KEYWORD | No |
| FACET | Yes |

### Arguments

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `field` | `string` | Yes | FACET field name. |
| `path` | `string` | Yes | Root path to expand. Must start with `/`. |
| `depth` | `number` | No | Levels to traverse (`>= 1`). Default: `1`. |
| `size` | `number` | No | Max children per level. Default: `10`. |
| `minDocCount` | `number` | No | Minimum docs required for a child bucket. Default: `1`. |
| `order` | `object` | No | One-key order object: `{ "count": "asc\|desc" }` or `{ "path": "asc\|desc" }`. Default: `{ "count": "desc" }`. |

<Tabs>

<Tab title="TypeScript">
```ts
await index.aggregate({
  aggregations: {
    by_category: {
      $facet: {
        field: "category",
        path: "/category",
      },
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.aggregate(
    aggregations={
        "by_category": {
            "$facet": {
                "field": "category",
                "path": "/category",
            }
        }
    }
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"by_category": {"$facet": {"field": "category", "path": "/category"}}}'
```
</Tab>

</Tabs>

### Output

```json
{
  "by_category": {
    "path": "/category",
    "sumOtherDocCount": 0,
    "children": [
      { "path": "/category/books", "docCount": 12, "sumOtherDocCount": 0 },
      { "path": "/category/electronics", "docCount": 9, "sumOtherDocCount": 0 }
    ]
  }
}
```

* Increase `depth` for multi-level facet trees.
* Use `size` and `minDocCount` to control noise and response size.
* Use `order` to sort by count or by path.

<Tabs>

<Tab title="TypeScript">
```ts
await index.aggregate({
  aggregations: {
    category_tree: {
      $facet: {
        field: "category",
        path: "/category",
        depth: 2,
        size: 10,
        minDocCount: 1,
        order: { count: "desc" },
      },
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.aggregate(
    aggregations={
        "category_tree": {
            "$facet": {
                "field": "category",
                "path": "/category",
                "depth": 2,
                "size": 10,
                "minDocCount": 1,
                "order": {"count": "desc"},
            }
        }
    }
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"category_tree": {"$facet": {"field": "category", "path": "/category", "depth": 2, "size": 10, "minDocCount": 1, "order": {"count": "desc"}}}}'
```
</Tab>

</Tabs>

With `depth: 2`, the response includes nested children:

```json
{
  "by_cat": {
    "path": "/category",
    "sumOtherDocCount": 0,
    "children": [
      {
        "docCount": 2,
        "path": "/category/books",
        "sumOtherDocCount": 0,
        "children": [
          { "docCount": 1, "path": "/category/books/fiction", "sumOtherDocCount": 0 },
          { "docCount": 1, "path": "/category/books/nonfiction", "sumOtherDocCount": 0 }
        ]
      },
      {
        "docCount": 2,
        "path": "/category/electronics",
        "sumOtherDocCount": 0,
        "children": [
          { "docCount": 1, "path": "/category/electronics/laptops", "sumOtherDocCount": 0 },
          { "docCount": 1, "path": "/category/electronics/phones", "sumOtherDocCount": 0 }
        ]
      }
    ]
  }
}
```

* `$facet` cannot contain `$aggs`.
* `$facet` cannot be used as a sub-aggregation inside another `$aggs` block.

# $histogram
Source: https://upstash.com/docs/redis/search/aggregation-operators/bucket-aggregations/histogram

`$histogram` groups numeric values into fixed-width interval buckets.

Use it for distribution charts when each bucket has the same width.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | No |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | No |
| KEYWORD | No |
| FACET | No |

Field must be `FAST`.

### Arguments

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `field` | `string` | Yes | Field to bucket on. |
| `interval` | `number` | Yes | Bucket width. |
| `offset` | `number` | No | Shift bucket boundaries. |
| `minDocCount` | `number` | No | Exclude buckets with fewer docs. |
| `hardBounds` | `{ min: number, max: number }` | No | Hard clamp for bucket range. |
| `extendedBounds` | `{ min: number, max: number }` | No | Emit buckets across this range, including empty ones. |
| `keyed` | `boolean` | No | If `true`, returns buckets as an object. Default: `false`. |

For `hardBounds` and `extendedBounds`, both `min` and `max` are required.

<Tabs>

<Tab title="TypeScript">
```ts
await index.aggregate({
  aggregations: {
    price_distribution: {
      $histogram: { field: "price", interval: 25 },
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.aggregate(
    aggregations={
        "price_distribution": {
            "$histogram": {"field": "price", "interval": 25}
        }
    }
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"price_distribution": {"$histogram": {"field": "price", "interval": 25}}}'
```
</Tab>

</Tabs>

### Output

```json
{
  "price_distribution": {
    "buckets": [
      { "key": 0, "docCount": 3 },
      { "key": 25, "docCount": 4 },
      { "key": 50, "docCount": 2 }
    ]
  }
}
```

Bucket entries can include `keyAsString` and nested sub-aggregation outputs.

# Overview
Source: https://upstash.com/docs/redis/search/aggregation-operators/bucket-aggregations/overview

Bucket aggregations group matching documents into buckets.

Use bucket aggregations when you want segmented analytics (for example by category, price tier, or time window).

### Available Operators

| Operator | What it groups by |
|----------|-------------------|
| [`$terms`](./terms) | Distinct field values |
| [`$range`](./range) | Explicit `from` / `to` ranges |
| [`$histogram`](./histogram) | Fixed numeric intervals |
| [`$dateHistogram`](./date-histogram) | Fixed date/time intervals |
| [`$facet`](./facet) | Hierarchical FACET paths |

### Input Format

Every bucket operator takes an object with a `field` property and operator-specific parameters:

**`$terms`** — group by distinct values:
```json
{"by_category": {"$terms": {"field": "category", "size": 10}}}
```

**`$range`** — custom range buckets:
```json
{"price_ranges": {"$range": {"field": "price", "ranges": [{"to": 50}, {"from": 50, "to": 100}, {"from": 100}]}}}
```

**`$histogram`** — fixed numeric intervals:
```json
{"price_buckets": {"$histogram": {"field": "price", "interval": 10}}}
```

**`$dateHistogram`** — fixed time intervals:
```json
{"by_month": {"$dateHistogram": {"field": "createdAt", "fixedInterval": "30d"}}}
```

### Behavior Notes

* Bucket operators can contain nested `$aggs` for per-bucket metrics.
* `$terms`, `$range`, `$histogram`, and `$dateHistogram` support nested `$aggs`.
* `$facet` does not support nested `$aggs` and cannot be used as a sub-aggregation.

# $range
Source: https://upstash.com/docs/redis/search/aggregation-operators/bucket-aggregations/range

`$range` groups documents into custom ranges.

Use it when bucket boundaries are defined by business logic (for example pricing tiers).

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | No |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | No |
| KEYWORD | No |
| FACET | No |

Field must be `FAST`.

### Arguments

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `field` | `string` | Yes | Field to bucket on. |
| `ranges` | `Array<{ key?: string, from?: number, to?: number }>` | Yes | Non-empty array of range objects. |
| `keyed` | `boolean` | No | If `true`, returns buckets as an object. Default: `false`. |

`from` is inclusive. `to` is exclusive.

<Tabs>

<Tab title="TypeScript">
```ts
await index.aggregate({
  aggregations: {
    price_tiers: {
      $range: {
        field: "price",
        ranges: [{ to: 30 }, { from: 30, to: 60 }, { from: 60 }],
      },
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.aggregate(
    aggregations={
        "price_tiers": {
            "$range": {
                "field": "price",
                "ranges": [{"to": 30}, {"from": 30, "to": 60}, {"from": 60}],
            }
        }
    }
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"price_tiers": {"$range": {"field": "price", "ranges": [{"to": 30}, {"from": 30, "to": 60}, {"from": 60}]}}}'
```
</Tab>

</Tabs>

### Output

```json
{
  "price_tiers": {
    "buckets": [
      { "key": "*-30", "docCount": 3, "to": 30 },
      { "key": "30-60", "docCount": 3, "from": 30, "to": 60 },
      { "key": "60-*", "docCount": 3, "from": 60 }
    ]
  }
}
```

Bucket entries can include `fromAsString` / `toAsString` and nested sub-aggregation outputs.

# $terms
Source: https://upstash.com/docs/redis/search/aggregation-operators/bucket-aggregations/terms

`$terms` groups documents by distinct field values.

Each distinct value becomes one bucket with its own `docCount`.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | No |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | Yes |
| KEYWORD | Yes |
| FACET | No |

Field must be `FAST`.

### Arguments

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `field` | `string` | Yes | Field to bucket on. |
| `size` | `number` | No | Max number of buckets returned. |
| `segmentSize` | `number` | No | Number of candidate terms collected per index segment before merging. Raising it improves count accuracy at higher cost; lowering it is cheaper but less accurate. See [Count accuracy](#count-accuracy). |
| `showTermDocCountError` | `boolean` | No | Include `docCountErrorUpperBound` in output. |
| `minDocCount` | `number` | No | Buckets with fewer docs are excluded. |
| `order` | `object` | No | One-key order object: `{ "count": "desc" }`, `{ "key": "asc" }`, or `{ "<subAggAlias>": "desc" }`. |
| `missing` | `string \| number` | No | Bucket key used when the field is missing. |
| `include` | `string \| string[]` | No | Include regex or explicit value list. |
| `exclude` | `string \| string[]` | No | Exclude regex or explicit value list. |

<Tabs>

<Tab title="TypeScript">
```ts
await index.aggregate({
  aggregations: {
    by_category: {
      $terms: { field: "category", size: 5 },
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.aggregate(
    aggregations={
        "by_category": {
            "$terms": {"field": "category", "size": 5}
        }
    }
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"by_category": {"$terms": {"field": "category", "size": 5}}}'
```
</Tab>

</Tabs>

### Output

```json
{
  "by_category": {
    "buckets": [
      { "key": "electronics", "docCount": 120 },
      { "key": "books", "docCount": 83 }
    ],
    "sumOtherDocCount": 42
  }
}
```

* `sumOtherDocCount` is the number of matching documents that fell outside the returned buckets.

### Count accuracy

<Warning>
`$terms` bucket counts are **approximate**. Terms are tallied per index segment and then merged, so a term's `docCount` can be slightly off, especially for terms near the `size` cutoff.
</Warning>

`docCountErrorUpperBound` reports the maximum possible error on the returned counts (set `showTermDocCountError: true` for per-bucket values), and `sumOtherDocCount` is the number of matching documents not included in the returned buckets. Raising `segmentSize` reduces the error at the cost of more work per query. For an exact count of a specific term, use [`SEARCH.COUNT`](/docs/redis/commands/search/search-count) instead of reading it from a bucket.

# $avg
Source: https://upstash.com/docs/redis/search/aggregation-operators/metric-aggregations/avg

`$avg` computes the arithmetic mean of a field across matching documents.

If `missing` is provided, documents without the field are treated as if they had the `missing` value.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | No |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | Yes |
| KEYWORD | No |
| FACET | No |

Field must be `FAST`.

### Arguments

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `field` | `string` | Yes | Field to aggregate. |
| `missing` | `number` | No | Fallback value for missing fields. |

<Tabs>

<Tab title="TypeScript">
```ts
await index.aggregate({
  aggregations: {
    avg_price: { $avg: { field: "price", missing: 0 } },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.aggregate(
    aggregations={"avg_price": {"$avg": {"field": "price", "missing": 0}}}
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"avg_price": {"$avg": {"field": "price", "missing": 0}}}'
```
</Tab>

</Tabs>

### Output

```json
{ "avg_price": { "value": 40 } }
```

`value` is `null` when no values are available after filtering.

# $cardinality
Source: https://upstash.com/docs/redis/search/aggregation-operators/metric-aggregations/cardinality

`$cardinality` returns the number of distinct values in a field.

All matching field values are deduplicated, then counted.

If `missing` is provided, documents without the field are treated as that fallback key
(which can increase the distinct count by at most one extra value).

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | No |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | Yes |
| KEYWORD | Yes |
| FACET | No |

Field must be `FAST`.

### Arguments

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `field` | `string` | Yes | Field to aggregate. |
| `missing` | `string \| number` | No | Fallback key for missing fields. |

<Tabs>

<Tab title="TypeScript">
```ts
await index.aggregate({
  aggregations: {
    unique_users: { $cardinality: { field: "userId" } },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.aggregate(
    aggregations={"unique_users": {"$cardinality": {"field": "userId"}}}
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE events '{}' '{"unique_users": {"$cardinality": {"field": "userId"}}}'
```
</Tab>

</Tabs>

### Output

```json
{ "unique_categories": { "value": 3 } }
```

# $count
Source: https://upstash.com/docs/redis/search/aggregation-operators/metric-aggregations/count

`$count` returns how many documents contribute a value for a field.

### How It Works

* By default, only documents that have the field are counted.
* If `missing` is set, documents without the field also contribute to result.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | No |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | Yes |
| KEYWORD | Yes |
| FACET | No |

Field must be `FAST`.

### Arguments

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `field` | `string` | Yes | Field to count values for. |
| `missing` | `number` | No | Fallback value for missing fields. |

<Tabs>

<Tab title="TypeScript">
```ts
await index.aggregate({
  aggregations: {
    price_count: { $count: { field: "price" } },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.aggregate(
    aggregations={"price_count": {"$count": {"field": "price"}}}
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"price_count": {"$count": {"field": "price"}}}'
```
</Tab>

</Tabs>

### Output

```json
{ "price_count": { "value": 9 } }
```

# $extendedStats
Source: https://upstash.com/docs/redis/search/aggregation-operators/metric-aggregations/extended-stats

`$extendedStats` returns `$stats` plus additional distribution metrics.

In addition to `count`, `sum`, `min`, `max`, and `avg`, this operator provides:

* `sumOfSquares`
* `variance`, `variancePopulation`, `varianceSampling`
* `stdDeviation`, `stdDeviationPopulation`, `stdDeviationSampling`
* `stdDeviationBounds` (`upper`, `lower`, plus sampling/population variants)

`sigma` controls the width of `stdDeviationBounds`.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | No |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | No |
| KEYWORD | No |
| FACET | No |

Field must be `FAST`.

### Arguments

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `field` | `string` | Yes | Field to aggregate. |
| `missing` | `number` | No | Fallback value for missing fields. |
| `sigma` | `number` | No | Multiplier used for standard deviation bounds. |

<Tabs>

<Tab title="TypeScript">
```ts
await index.aggregate({
  aggregations: {
    price_extended: {
      $extendedStats: { field: "price", sigma: 2, missing: 0 },
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.aggregate(
    aggregations={
        "price_extended": {
            "$extendedStats": {"field": "price", "sigma": 2, "missing": 0}
        }
    }
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"price_extended": {"$extendedStats": {"field": "price", "sigma": 2, "missing": 0}}}'
```
</Tab>

</Tabs>

### Output

Returns a single object containing all basic and extended fields. Example shape:

```json
{ "price_extended": {
    "count": 9, "min": 0, "max": 80, "avg": 40, "sum": 360,
    "sumOfSquares": 18000, "variance": 200, "stdDeviation": 14.14,
    "stdDeviationBounds": { "upper": 68.28, "lower": 11.72 }
  }
}
```

# $max
Source: https://upstash.com/docs/redis/search/aggregation-operators/metric-aggregations/max

`$max` returns the maximum field value across matching documents.

If `missing` is set, missing fields are treated as that value.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | No |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | Yes |
| KEYWORD | No |
| FACET | No |

Field must be `FAST`.

### Arguments

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `field` | `string` | Yes | Field to aggregate. |
| `missing` | `number` | No | Fallback value for missing fields. |

<Tabs>

<Tab title="TypeScript">
```ts
await index.aggregate({
  aggregations: {
    most_expensive: { $max: { field: "price", missing: 0 } },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.aggregate(
    aggregations={"most_expensive": {"$max": {"field": "price", "missing": 0}}}
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"most_expensive": {"$max": {"field": "price", "missing": 0}}}'
```
</Tab>

</Tabs>

### Output

```json
{ "most_expensive": { "value": 80 } }
```

`value` can be `null` when no values are available.

# $min
Source: https://upstash.com/docs/redis/search/aggregation-operators/metric-aggregations/min

`$min` returns the minimum field value across matching documents.

If `missing` is set, missing fields are treated as that value.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | No |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | Yes |
| KEYWORD | No |
| FACET | No |

Field must be `FAST`.

### Arguments

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `field` | `string` | Yes | Field to aggregate. |
| `missing` | `number` | No | Fallback value for missing fields. |

<Tabs>

<Tab title="TypeScript">
```ts
await index.aggregate({
  aggregations: {
    cheapest: { $min: { field: "price", missing: 0 } },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.aggregate(
    aggregations={"cheapest": {"$min": {"field": "price", "missing": 0}}}
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"cheapest": {"$min": {"field": "price", "missing": 0}}}'
```
</Tab>

</Tabs>

### Output

```json
{ "cheapest": { "value": 0 } }
```

`value` can be `null` when no values are available.

# Overview
Source: https://upstash.com/docs/redis/search/aggregation-operators/metric-aggregations/overview

Metric aggregations compute summary numbers over all documents that matched your `filter`.

Use metrics when you want one value (or stats object), not grouped buckets.

### Available Operators

| Operator | What it computes |
|----------|------------------|
| [`$avg`](./avg) | Arithmetic mean |
| [`$sum`](./sum) | Total of values |
| [`$min`](./min) | Smallest value |
| [`$max`](./max) | Largest value |
| [`$count`](./count) | Count of values |
| [`$cardinality`](./cardinality) | Count of distinct values |
| [`$stats`](./stats) | `count`, `sum`, `min`, `max`, `avg` |
| [`$extendedStats`](./extended-stats) | `$stats` + variance and std deviation metrics |
| [`$percentiles`](./percentiles) | Distribution percent points |

### Input Format

Every metric operator takes an object with at least a `field` property:

```json
{"alias_name": {"$avg": {"field": "price"}}}
{"alias_name": {"$avg": {"field": "price", "missing": 0}}}
```

The `field` value must be a string pointing to a FAST field in your schema.

### Behavior Notes

* Metric operators require a `field`.
* The field must be `FAST` in your schema.
* Metric operators do not support nested `$aggs`.
* For many metric operators, `missing` lets you provide a fallback value for documents where the field does not exist.

# $percentiles
Source: https://upstash.com/docs/redis/search/aggregation-operators/metric-aggregations/percentiles

`$percentiles` returns percentile cut points for a field.

A percentile tells you the value below which a percentage of observations fall.

* `50`th percentile: median-like center point
* `95`th percentile: tail latency/price threshold
* `99`th percentile: extreme tail threshold

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | No |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | No |
| KEYWORD | No |
| FACET | No |

Field must be `FAST`.

### Arguments

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `field` | `string` | Yes | Field to aggregate. |
| `percents` | `number[]` | No | Percent points to calculate (for example `[50, 95, 99]`). |
| `keyed` | `boolean` | No | If `true` (default), returns a map. If `false`, returns an array of `{ key, value }`. |
| `missing` | `number` | No | Fallback value for missing fields. |

If `percents` is omitted, defaults to `[1.0, 5.0, 25.0, 50.0, 75.0, 95.0, 99.0]`.

<Tabs>

<Tab title="TypeScript">
```ts
await index.aggregate({
  aggregations: {
    latency_pct: {
      $percentiles: { field: "latencyMs", percents: [50, 95, 99], keyed: true },
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.aggregate(
    aggregations={
        "latency_pct": {
            "$percentiles": {
                "field": "latencyMs",
                "percents": [50, 95, 99],
                "keyed": True,
            }
        }
    }
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE logs '{}' '{"latency_pct": {"$percentiles": {"field": "latencyMs", "percents": [50, 95, 99], "keyed": true}}}'
```
</Tab>

</Tabs>

### Output

`keyed: true` (default):

```json
{
  "values": {
    "50.0": 40,
    "95.0": 76,
    "99.0": 79.2
  }
}
```

`keyed: false`:

```json
{
  "values": [
    { "key": 50, "value": 40 },
    { "key": 95, "value": 76 },
    { "key": 99, "value": 79.2 }
  ]
}
```

# $stats
Source: https://upstash.com/docs/redis/search/aggregation-operators/metric-aggregations/stats

`$stats` returns multiple basic metrics in one pass.

For the selected field, it computes:

* `count`
* `sum`
* `min`
* `max`
* `avg`

This is equivalent to running `$count`, `$sum`, `$min`, `$max`, and `$avg` together, but returned as a single object.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | No |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | Yes |
| KEYWORD | No |
| FACET | No |

Field must be `FAST`.

### Arguments

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `field` | `string` | Yes | Field to aggregate. |
| `missing` | `number` | No | Fallback value for missing fields. |

<Tabs>

<Tab title="TypeScript">
```ts
await index.aggregate({
  aggregations: {
    price_stats: { $stats: { field: "price", missing: 0 } },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.aggregate(
    aggregations={"price_stats": {"$stats": {"field": "price", "missing": 0}}}
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"price_stats": {"$stats": {"field": "price", "missing": 0}}}'
```
</Tab>

</Tabs>

### Output

```json
{ "price_stats": { "count": 9, "min": 0, "max": 80, "sum": 360, "avg": 40 } }
```

`min`, `max`, and `avg` can be `null` when no values are available.

# $sum
Source: https://upstash.com/docs/redis/search/aggregation-operators/metric-aggregations/sum

`$sum` computes the total of a field across matching documents.

If `missing` is set, missing fields contribute that fallback value.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | No |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | Yes |
| KEYWORD | No |
| FACET | No |

Field must be `FAST`.

### Arguments

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `field` | `string` | Yes | Field to aggregate. |
| `missing` | `number` | No | Fallback value for missing fields. |

<Tabs>

<Tab title="TypeScript">
```ts
await index.aggregate({
  aggregations: {
    total_price: { $sum: { field: "price", missing: 0 } },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.aggregate(
    aggregations={"total_price": {"$sum": {"field": "price", "missing": 0}}}
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"total_price": {"$sum": {"field": "price", "missing": 0}}}'
```
</Tab>

</Tabs>

### Output

```json
{ "total_price": { "value": 360 } }
```

`value` can be `null` when no values are available.

# Aggregations
Source: https://upstash.com/docs/redis/search/aggregations

Aggregations let you compute analytics over your indexed data — metrics like averages, sums, and statistics,
as well as bucket-based groupings like terms, ranges, and histograms.

Use the [`SEARCH.AGGREGATE`](/docs/redis/commands/search/search-aggregate) command to run aggregations.

They are useful when you want to answer questions like:

* "What is the average price?"
* "How many unique users do we have?"
* "How many orders fall into each price range?"
* "How does traffic change per hour/day?"

<Tabs>

<Tab title="TypeScript">
```ts
const result = await index.aggregate({
  aggregations: {
    avg_price: { $avg: { field: "price" } },
  },
});
```
</Tab>

<Tab title="Python">
```python
result = index.aggregate(
    aggregations={"avg_price": {"$avg": {"field": "price"}}},
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"avg_price": {"$avg": {"field": "price"}}}'
```
</Tab>

</Tabs>

Aggregation requests have two phases:

1. **Document selection**: Optional `filter` selects which documents participate.
2. **Aggregation computation**: the selected set is reduced into metric values and/or buckets.

Each aggregation is defined with an **alias** (the key you choose for the result) and an **operator** that specifies what to compute.

### Response Format

<Tabs>

<Tab title="TypeScript">
```ts
const result = await index.aggregate({
  aggregations: {
    avg_price: { $avg: { field: "price" } },
    by_category: { $terms: { field: "category", size: 5 } },
  },
});

// result is an object keyed by alias:
// {
//   avg_price: { value: 49.99 },
//   by_category: {
//     buckets: [
//       { key: "electronics", docCount: 42 },
//       { key: "clothing", docCount: 31 },
//     ],
//   },
// }
```
</Tab>

<Tab title="Python">
```python
result = index.aggregate(
    aggregations={
        "avg_price": {"$avg": {"field": "price"}},
        "by_category": {"$terms": {"field": "category", "size": 5}},
    },
)

# result is a dict keyed by alias:
# {
#   "avg_price": {"value": 49.99},
#   "by_category": {
#     "buckets": [
#       {"key": "electronics", "docCount": 42},
#       {"key": "clothing", "docCount": 31},
#     ],
#   },
# }
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"avg_price": {"$avg": {"field": "price"}}, "by_category": {"$terms": {"field": "category", "size": 5}}}'

# Response (`redis-cli --json`) is an object keyed by alias:
# {"avg_price":{"value":49.99},"by_category":{"buckets":[{"key":"electronics","docCount":42},{"key":"clothing","docCount":31}]}}
```
</Tab>

</Tabs>

## Filtering

Use `filter` to restrict which documents participate in the aggregation.

Filtering uses the same query syntax as [queries](./querying).

<Tabs>

<Tab title="TypeScript">
```ts
const result = await index.aggregate({
  filter: { inStock: true },
  aggregations: {
    avg_price: { $avg: { field: "price" } },
  },
});
```
</Tab>

<Tab title="Python">
```python
result = index.aggregate(
    filter={"inStock": True},
    aggregations={
        "avg_price": {"$avg": {"field": "price"}},
    },
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{"inStock": true}' '{"avg_price": {"$avg": {"field": "price"}}}'
```
</Tab>

</Tabs>

## Multiple Aggregations in One Request

You can compute multiple top-level aggregations in one call by defining multiple aliases under `aggregations`.

Each alias is computed against the same filtered document set.

<Tabs>

<Tab title="TypeScript">
```ts
const result = await index.aggregate({
  aggregations: {
    avg_price: { $avg: { field: "price" } },
    price_stats: { $stats: { field: "price" } },
    by_category: { $terms: { field: "category", size: 5 } },
    price_ranges: {
      $range: {
        field: "price",
        ranges: [{ to: 50 }, { from: 50 }],
      },
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
result = index.aggregate(
    aggregations={
        "avg_price": {"$avg": {"field": "price"}},
        "price_stats": {"$stats": {"field": "price"}},
        "by_category": {"$terms": {"field": "category", "size": 5}},
        "price_ranges": {
            "$range": {
                "field": "price",
                "ranges": [{"to": 50}, {"from": 50}],
            }
        },
    },
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"avg_price": {"$avg": {"field": "price"}}, "price_stats": {"$stats": {"field": "price"}}, "by_category": {"$terms": {"field": "category", "size": 5}}, "price_ranges": {"$range": {"field": "price", "ranges": [{"to": 50}, {"from": 50}]}}}'
```
</Tab>

</Tabs>

## Nested Aggregations

Bucket operators can include sub-aggregations via `$aggs`, so you can compute per-bucket metrics.

* `$terms`, `$range`, `$histogram`, and `$dateHistogram` support nested `$aggs`.
* `$facet` does not support nested `$aggs`.
* Metric operators do not support nested `$aggs`.

<Tabs>

<Tab title="TypeScript">
```ts
const result = await index.aggregate({
  aggregations: {
    by_category: {
      $terms: { field: "category" },
      $aggs: {
        avg_price: { $avg: { field: "price" } },
        min_price: { $min: { field: "price" } },
      },
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
result = index.aggregate(
    aggregations={
        "by_category": {
            "$terms": {"field": "category"},
            "$aggs": {
                "avg_price": {"$avg": {"field": "price"}},
                "min_price": {"$min": {"field": "price"}},
            },
        },
    },
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.AGGREGATE products '{}' '{"by_category": {"$terms": {"field": "category"}, "$aggs": {"avg_price": {"$avg": {"field": "price"}}, "min_price": {"$min": {"field": "price"}}}}}'
```
</Tab>

</Tabs>

## Operator Families

### Metric Aggregations

Metric aggregations return numeric summaries.

| Operator | Typical use |
|----------|-------------|
| [`$avg`](./aggregation-operators/metric-aggregations/avg) | Mean value |
| [`$sum`](./aggregation-operators/metric-aggregations/sum) | Total |
| [`$min`](./aggregation-operators/metric-aggregations/min) | Smallest value |
| [`$max`](./aggregation-operators/metric-aggregations/max) | Largest value |
| [`$count`](./aggregation-operators/metric-aggregations/count) | Number of values |
| [`$cardinality`](./aggregation-operators/metric-aggregations/cardinality) | Number of distinct values |
| [`$stats`](./aggregation-operators/metric-aggregations/stats) | Basic summary stats |
| [`$extendedStats`](./aggregation-operators/metric-aggregations/extended-stats) | Stats + variance/std deviation |
| [`$percentiles`](./aggregation-operators/metric-aggregations/percentiles) | Distribution cut points |

See the [metric overview](./aggregation-operators/metric-aggregations/overview).

### Bucket Aggregations

Bucket aggregations partition documents into groups.

| Operator | Typical use |
|----------|-------------|
| [`$terms`](./aggregation-operators/bucket-aggregations/terms) | Top values by field |
| [`$range`](./aggregation-operators/bucket-aggregations/range) | Custom ranges |
| [`$histogram`](./aggregation-operators/bucket-aggregations/histogram) | Fixed numeric bins |
| [`$dateHistogram`](./aggregation-operators/bucket-aggregations/date-histogram) | Fixed time bins |
| [`$facet`](./aggregation-operators/bucket-aggregations/facet) | Hierarchical facets |

See the [bucket overview](./aggregation-operators/bucket-aggregations/overview).

# Aliases
Source: https://upstash.com/docs/redis/search/aliases

Aliases let you create alternative names for your search indices. This is useful for zero-downtime reindexing — you can build a new index, then atomically swap the alias to point to it.

***

### Adding an Alias

The [`SEARCH.ALIASADD`](/docs/redis/commands/search/search-aliasadd) command creates a new alias or updates an existing alias to point to a different index.

Returns `1` if a new alias was created, or `2` if an existing alias was updated to point to a new index.

<Tabs>

<Tab title="TypeScript">
```ts
// Top-level: create or update an alias
const result = await redis.search.alias.add({
  indexName: "products-v2",
  alias: "products",
});

// Via index method
const result2 = await index.addAlias({ alias: "products" });
```
</Tab>

<Tab title="Python">
```python
# Top-level: create or update an alias
result = redis.search.alias.add(index_name="products-v2", alias="products")

# Via index method
result = index.add_alias(alias="products")
```
</Tab>

<Tab title="Redis CLI">
```bash
# Create or update an alias
SEARCH.ALIASADD products products-v2
```
</Tab>

</Tabs>

A common pattern is to use aliases for zero-downtime reindexing:

<Tabs>

<Tab title="TypeScript">
```ts
// 1. Create a new index with updated schema
const productsV2 = await redis.search.createIndex({
  name: "products-v2",
  dataType: "json",
  prefix: "product:",
  schema,
});

// 2. Wait for indexing to complete
await productsV2.waitIndexing();

// 3. Swap the alias to point to the new index
await redis.search.alias.add({
  indexName: "products-v2",
  alias: "products",
});

// 4. Drop the old index
const oldIndex = redis.search.index({ name: "products-v1" });
await oldIndex.drop();
```
</Tab>

<Tab title="Python">
```python
# 1. Create a new index with updated schema
products_v2 = redis.search.create_index(
    name="products-v2",
    data_type="json",
    prefix="product:",
    schema=schema,
)

# 2. Wait for indexing to complete
products_v2.wait_indexing()

# 3. Swap the alias to point to the new index
redis.search.alias.add(index_name="products-v2", alias="products")

# 4. Drop the old index
old_index = redis.search.index(name="products-v1")
old_index.drop()
```
</Tab>

<Tab title="Redis CLI">
```bash
# 1. Create new index
SEARCH.CREATE products-v2 ON JSON PREFIX 1 product: SCHEMA name TEXT price F64 FAST

# 2. Wait for indexing
SEARCH.WAITINDEXING products-v2

# 3. Swap alias
SEARCH.ALIASADD products products-v2

# 4. Drop old index
SEARCH.DROP products-v1
```
</Tab>

</Tabs>

***

### Deleting an Alias

The [`SEARCH.ALIASDEL`](/docs/redis/commands/search/search-aliasdel) command removes an alias.

Returns `1` if the alias was deleted, or `0` if the alias was not found.

<Tabs>

<Tab title="TypeScript">
```ts
const result = await redis.search.alias.delete({ alias: "products" });
```
</Tab>

<Tab title="Python">
```python
result = redis.search.alias.delete(alias="products")
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.ALIASDEL products
```
</Tab>

</Tabs>

***

### Listing Aliases

The [`SEARCH.LISTALIASES`](/docs/redis/commands/search/search-listaliases) command returns all aliases and the indices they point to.

<Tabs>

<Tab title="TypeScript">
```ts
const aliases = await redis.search.alias.list();
// → { "products": "products-v2", "users": "users-v1" }
```
</Tab>

<Tab title="Python">
```python
aliases = redis.search.alias.list()
# → {"products": "products-v2", "users": "users-v1"}
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.LISTALIASES
# → [["products", "products-v2"], ["users", "users-v1"]]
```
</Tab>

</Tabs>

# Command Reference
Source: https://upstash.com/docs/redis/search/command-reference

<Note>
  Upstash Redis Search uses `SEARCH.*` commands. They are separate from and incompatible with the `FT.*` commands in the open-source RediSearch module.
</Note>

<Card title="Browse Search commands" icon="magnifying-glass" href="/redis/commands/search/overview" horizontal>
  View the complete Upstash Redis Search command reference.
</Card>

# Counting
Source: https://upstash.com/docs/redis/search/counting

The [`SEARCH.COUNT`](/docs/redis/commands/search/search-count) command returns the number of documents matching a query without retrieving them.

You can use `SEARCH.COUNT` for analytics, pagination UI (showing "X results found"),
or validating queries before retrieving results.

<Tabs>

<Tab title="TypeScript">
```ts
// Count all electronics
await products.count({
  filter: {
    category: "electronics",
  },
});

// Count in-stock items under $100
await products.count({
  filter: {
    inStock: true,
    price: { $lt: 100 },
  },
});
```
</Tab>

<Tab title="Python">
```python
# Count all electronics
products.count(filter={"category": "electronics"})

# Count in-stock items under $100
products.count(filter={"inStock": True, "price": {"$lt": 100}})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Count all electronics
SEARCH.COUNT products '{"category": "electronics"}'

# Count in-stock items under $100
SEARCH.COUNT products '{"inStock": true, "price": {"$lt": 100}}'
```
</Tab>

</Tabs>

# Document Updates
Source: https://upstash.com/docs/redis/search/document-updates

Documents in a search index are regular Redis keys.

When a key matches an index prefix, it is indexed automatically. When the key changes, the index is updated automatically.

<Note>
There is no separate update command for search. Use regular Redis write commands to change your data.
</Note>

Any command that modifies a matching key updates the index. For example, this includes commands such as:

* JSON commands such as `JSON.SET`, `JSON.MERGE`, ...
* Hash commands such as `HSET`, `HINCRBY`, ...
* String commands such as `SET`, `SETEX`, ...
* Generic commands such as `DEL`, `EXPIRE`, `PEXPIRE`, ...

This is not an exhaustive list. The rule is that if the command changes a key tracked by the index, the index is updated.

Index updates are batched for performance, so recent writes may not immediately appear in search results.
Use [`SEARCH.WAITINDEXING`](/docs/redis/commands/search/search-waitindexing) when you need to ensure queries reflect recent changes.

## JSON Documents

For JSON indexes, any JSON command that modifies a matching key updates the indexed document.

<Tabs>

<Tab title="TypeScript">
```ts
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const products = redis.search.index({ name: "products" });

await redis.json.set("product:1", "$.price", 79.99);

await products.waitIndexing();

const discounted = await products.query({
  filter: { price: { $lt: 90 } },
});
```
</Tab>

<Tab title="Python">
```python
from upstash_redis import Redis

redis = Redis.from_env()
products = redis.search.index(name="products")

redis.json.set("product:1", "$.price", 79.99)

products.wait_indexing()

discounted = products.query(filter={"price": {"$lt": 90}})
```
</Tab>

<Tab title="Redis CLI">
```bash
JSON.SET product:1 $.price 79.99

SEARCH.WAITINDEXING products
SEARCH.QUERY products '{"price":{"$lt":90}}'
```
</Tab>

</Tabs>

## Hash Documents

For hash indexes, any hash command that modifies a matching key updates the indexed document.

<Tabs>

<Tab title="TypeScript">
```ts
const articles = redis.search.index({ name: "articles" });

await redis.hset("article:1", {
  status: "published",
  views: 125,
});

await articles.waitIndexing();

const published = await articles.query({
  filter: { status: "published" },
});
```
</Tab>

<Tab title="Python">
```python
articles = redis.search.index(name="articles")

redis.hset("article:1", values={
    "status": "published",
    "views": 125,
})

articles.wait_indexing()

published = articles.query(filter={"status": "published"})
```
</Tab>

<Tab title="Redis CLI">
```bash
HSET article:1 status published views 125

SEARCH.WAITINDEXING articles
SEARCH.QUERY articles '{"status":"published"}'
```
</Tab>

</Tabs>

## String Documents

For string indexes, indexed keys must contain valid JSON strings.
Any command that replaces or modifies the matching string key updates the indexed document.

<Tabs>

<Tab title="TypeScript">
```ts
const notes = redis.search.index({ name: "notes" });

await redis.set("note:1", {
  title: "Release checklist",
  status: "done",
});

await notes.waitIndexing();

const done = await notes.query({
  filter: { status: "done" },
});
```
</Tab>

<Tab title="Python">
```python
notes = redis.search.index(name="notes")

redis.set("note:1", '{"title":"Release checklist","status":"done"}')

notes.wait_indexing()

done = notes.query(filter={"status": "done"})
```
</Tab>

<Tab title="Redis CLI">
```bash
SET note:1 '{"title":"Release checklist","status":"done"}'

SEARCH.WAITINDEXING notes
SEARCH.QUERY notes '{"status":"done"}'
```
</Tab>

</Tabs>

## Deletes

Deleting a matching key removes the document from the index.

<Tabs>

<Tab title="TypeScript">
```ts
await redis.del("product:1");
await products.waitIndexing();
```
</Tab>

<Tab title="Python">
```python
redis.delete("product:1")
products.wait_indexing()
```
</Tab>

<Tab title="Redis CLI">
```bash
DEL product:1
SEARCH.WAITINDEXING products
```
</Tab>

</Tabs>

## Expiration

When a matching key expires, Upstash Redis Search removes the expired document from the index.

<Tabs>

<Tab title="TypeScript">
```ts
await redis.expire("product:flash-sale", 3600);
```
</Tab>

<Tab title="Python">
```python
redis.expire("product:flash-sale", 3600)
```
</Tab>

<Tab title="Redis CLI">
```bash
EXPIRE product:flash-sale 3600
```
</Tab>

</Tabs>

## Eviction

When an indexed key is evicted, Upstash Redis Search removes the document from the index. Once the Redis key is gone,
it no longer appears in search results.

# Quickstart
Source: https://upstash.com/docs/redis/search/getting-started

## 1. Create Index

<Tabs>

<Tab title="TypeScript">
```ts
import { Redis, s } from "@upstash/redis";

const redis = Redis.fromEnv();

const index = await redis.search.createIndex({
  name: "products",
  dataType: "json",
  prefix: "product:",
  schema: s.object({
    name: s.string(),
    description: s.string(),
    category: s.string().noTokenize(),
    price: s.number(),
    inStock: s.boolean(),
  }),
});
```
</Tab>

<Tab title="Python">
```python
from upstash_redis import Redis

redis = Redis.from_env()

index = redis.search.create_index(
    name="products",
    data_type="json",
    prefixes="product:",
    schema={
        "name": "TEXT",
        "description": "TEXT",
        "category": {"type": "TEXT", "notokenize": True},
        "price": "F64",
        "inStock": "BOOL",
    },
)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.CREATE products ON JSON PREFIX 1 product: SCHEMA name TEXT description TEXT category TEXT NOTOKENIZE price F64 FAST inStock BOOL
```
</Tab>

</Tabs>

<Note>
Create an index once, not on every request. `createIndex` throws if an index with
the same name already exists. To make setup safely re-runnable, pass `exists_ok=True`
in the Python SDK, or wrap the call in a `try/catch` in TypeScript.
</Note>

## 2. Add Data

Add data using standard Redis JSON commands. Any key matching the index prefix will be automatically indexed.

<Note>
Writes are indexed asynchronously: a `JSON.SET` returns before the document is
searchable. For demos and tests, call `waitIndexing()` / `wait_indexing()` to
block until pending updates are applied. In production, queries running on a later
request will normally hit an up-to-date index without waiting.
</Note>

<Tabs>

<Tab title="TypeScript">
```ts
await redis.json.set("product:1", "$", {
  name: "Wireless Headphones",
  description:
    "Premium noise-cancelling wireless headphones with 30-hour battery life",
  category: "electronics",
  price: 199.99,
  inStock: true,
});

await redis.json.set("product:2", "$", {
  name: "Running Shoes",
  description: "Lightweight running shoes with advanced cushioning technology",
  category: "sports",
  price: 129.99,
  inStock: true,
});

await index.waitIndexing();
```
</Tab>

<Tab title="Python">
```python
redis.json.set("product:1", "$", {
    "name": "Wireless Headphones",
    "description": "Premium noise-cancelling wireless headphones with 30-hour battery life",
    "category": "electronics",
    "price": 199.99,
    "inStock": True,
})

redis.json.set("product:2", "$", {
    "name": "Running Shoes",
    "description": "Lightweight running shoes with advanced cushioning technology",
    "category": "sports",
    "price": 129.99,
    "inStock": True,
})

index.wait_indexing()
```
</Tab>

<Tab title="Redis CLI">
```bash
JSON.SET product:1 $ '{"name": "Wireless Headphones", "description": "Premium noise-cancelling wireless headphones with 30-hour battery life", "category": "electronics", "price": 199.99, "inStock": true}'
JSON.SET product:2 $ '{"name": "Running Shoes", "description": "Lightweight running shoes with advanced cushioning technology", "category": "sports", "price": 129.99, "inStock": true}'

SEARCH.WAITINDEXING products
```
</Tab>

</Tabs>

## 3. Search Data

<Tabs>

<Tab title="TypeScript">
```ts
const results = await index.query({
  filter: { description: "wireless" },
});

const count = await index.count({
  filter: { price: { $lt: 150 } },
});
```
</Tab>

<Tab title="Python">
```python
results = index.query(filter={"description": "wireless"})

count = index.count(filter={"price": {"$lt": 150}})
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.QUERY products '{"description": "wireless"}'

SEARCH.COUNT products '{"price": {"$lt": 150}}'
```
</Tab>

</Tabs>

## Next Steps

<CardGroup cols={2}>
  <Card
    title="Schema Definition"
    icon="table-columns"
    href="/redis/search/schema-definition"
  >
    Define the fields you want to index and how they are matched
  </Card>
  <Card
    title="Querying"
    icon="magnifying-glass"
    href="/redis/search/querying"
  >
    Learn the JSON-based query language with filters and operators
  </Card>
  <Card
    title="Aggregations"
    icon="layer-group"
    href="/redis/search/aggregations"
  >
    Group and summarize your indexed data with aggregation pipelines
  </Card>
  <Card
    title="Recipes"
    icon="book-open"
    href="/redis/search/recipes/overview"
  >
    Complete, real-world examples you can adapt to your own use cases
  </Card>
</CardGroup>

# Indices
Source: https://upstash.com/docs/redis/search/index-management

An index is a data structure that enables **fast full-text search and filtering across your Redis data**. Without an index, searching requires scanning every key in your database: an operation that becomes very slow as your data grows.

When you create a search index, Upstash Redis builds an optimized lookup structure for the schema you specify. This allows queries to find matching documents in milliseconds, regardless of dataset size. The index automatically stays in sync with your data: any keys matching the index prefix are indexed when created, updated when modified, and removed from the index when deleted.

You define an index once by specifying:

* A **name** to identify the index
* A **prefix** pattern to determine which keys to track (e.g., `user:`)
* A **schema** describing which fields to index and their types

***

### Creating an Index

The [`SEARCH.CREATE`](/docs/redis/commands/search/search-create) command creates an index identified by a name, which must be a unique key in Redis. Each index works with a single key type (JSON, hash, or string).

<Tabs>

<Tab title="TypeScript">
```ts
import { Redis, s } from "@upstash/redis";

const redis = Redis.fromEnv();

// Basic index on JSON data
const users = await redis.search.createIndex({
  name: "users",
  dataType: "json",
  prefix: "user:",
  schema: s.object({
    name: s.string(),
    email: s.string(),
    age: s.number(),
  }),
});

```
</Tab>

<Tab title="Python">
```python
from upstash_redis import Redis

redis = Redis.from_env()

users = redis.search.create_index(
    name="users",
    data_type="json",
    prefix="user:",
    schema={
        "name": "TEXT",
        "email": "TEXT",
        "age": "U64",
    },
)
```
</Tab>

<Tab title="Redis CLI">
```bash
# Basic index on hash data
SEARCH.CREATE users on JSON PREFIX 1 user: SCHEMA name TEXT email TEXT age u64
```

</Tab>

</Tabs>

**We only create an index once**, for example inside of a script we run once. We do not recommend creating an index at runtime, for example inside of a serverless function.

* For **JSON** indices, an index field can be specified for fields on various nested levels.

* For **hash** indices, an index field can be specified for fields. As hash fields cannot have
  nesting on their own, for this kind of indices, only top-level schema fields can be used.

* For **string** indices, indexed keys must be valid JSON strings. A field on any nesting level
  can be indexed, similar to JSON indices.

<Tabs>

<Tab title="TypeScript">
```ts
import { Redis, s } from "@upstash/redis";
const redis = Redis.fromEnv();

// String index with nested schema fields
const comments = await redis.search.createIndex({
  name: "comments",
  dataType: "string",
  prefix: "comment:",
  schema: s.object({
    user: s.object({
      name: s.string(),
      email: s.string().noTokenize(),
    }),
    comment: s.string(),
    upvotes: s.number(),
    commentedAt: s.date().fast(),
  }),
});

```
</Tab>

<Tab title="Python">
```python
from upstash_redis import Redis

redis = Redis.from_env()

comments = redis.search.create_index(
    name="comments",
    data_type="string",
    prefix="comment:",
    schema={
        "user.name": "TEXT",
        "user.email": {"type": "TEXT", "notokenize": True},
        "comment": "TEXT",
        "upvotes": "U64",
        "commentedAt": {"type": "DATE", "fast": True},
    },
)
```
</Tab>

<Tab title="Redis CLI">
```bash
# String index with nested schema fields
SEARCH.CREATE comments ON STRING PREFIX 1 comment: SCHEMA user.name TEXT user.email TEXT NOTOKENIZE comment TEXT upvotes U64 FAST commentedAt DATE FAST
```

</Tab>

</Tabs>

It is possible to define an index for more than one prefix. However, there are some rules concerning the usage of
multiple prefixes:

* Prefixes must not contain duplicates.
* No prefix should cover another prefix (e.g., `user:` and `user:admin:` are not allowed together).
* Multiple distinct prefixes are allowed (e.g., `article:` and `blog:` are valid together).

<Tabs>

<Tab title="TypeScript">
```ts {4}
const articles = await redis.search.createIndex({
  name: "articles",
  dataType: "json",
  prefix: ["article:", "blog:", "news:"],
  schema: s.object({
    title: s.string(),
    body: s.string(),
    author: s.string().noStem(),
    publishedAt: s.date().fast(),
    viewCount: s.number(),
  }),
});

```
</Tab>

<Tab title="Python">
```python {4}
articles = redis.search.create_index(
    name="articles",
    data_type="json",
    prefix=["article:", "blog:", "news:"],
    schema={
        "title": "TEXT",
        "body": "TEXT",
        "author": {"type": "TEXT", "nostem": True},
        "publishedAt": {"type": "DATE", "fast": True},
        "viewCount": "U64",
    },
)
```
</Tab>

<Tab title="Redis CLI">
```bash
# String index with nested schema fields
SEARCH.CREATE comments ON STRING PREFIX 1 comment: SCHEMA user.name TEXT user.email TEXT NOTOKENIZE comment TEXT upvotes U64 FAST commentedAt DATE FAST
```

</Tab>

</Tabs>

By default, when an index is created, all existing keys matching the specified type and prefixes are scanned and indexed.
Use `SKIPINITIALSCAN` to defer indexing, which is useful for large datasets where you want to start fresh or handle
existing data differently. Run [`SEARCH.REINDEX`](/docs/redis/commands/search/search-reindex) later to rebuild the index from
the current matching keys.

<Tabs>

<Tab title="TypeScript">
```ts {5}
const profiles = await redis.search.createIndex({
  name: "profiles",
  dataType: "string",
  prefix: "profiles:",
  skipInitialScan: true,
  schema: s.object({
    name: s.string(),
  }),
});
```
</Tab>

<Tab title="Python">
```python {5}
profiles = redis.search.create_index(
    name="profiles",
    data_type="string",
    prefixes="profiles:",
    skip_initial_scan=True,
    schema={
        "name": "TEXT",
    },
)
```
</Tab>

<Tab title="Redis CLI">
```bash
# Skipping initial scan and indexing of keys with SKIPINITIALSCAN
SEARCH.CREATE profiles ON STRING PREFIX 1 profiles: SKIPINITIALSCAN SCHEMA name TEXT
```
</Tab>

</Tabs>

It is possible to specify the language of the text fields, so that an appropriate tokenizer
and stemmer can be used. For more on tokenization and stemming, see the
[Text Field Options](./schema-definition#text-field-options) section.

When not specified, language defaults to `english`.

Currently, the following languages are supported:

* `english`
* `arabic`
* `danish`
* `dutch`
* `finnish`
* `french`
* `german`
* `greek`
* `hungarian`
* `italian`
* `norwegian`
* `portuguese`
* `romanian`
* `russian`
* `spanish`
* `swedish`
* `tamil`
* `turkish`

<Tabs>

<Tab title="TypeScript">
```ts {5}
const addresses = await redis.search.createIndex({
  name: "addresses",
  dataType: "json",
  prefix: "address:",
  language: "turkish",
  schema: s.object({
    address: s.string().noStem(),
    description: s.string(),
  }),
});

```
</Tab>

<Tab title="Python">
```python {5}
addresses = redis.search.create_index(
    name="addresses",
    data_type="json",
    prefix="address:",
    language="turkish",
    schema={
        "address": {"type": "TEXT", "nostem": True},
        "description": "TEXT",
    },
)
```
</Tab>

<Tab title="Redis CLI">
```bash
# Turkish language index
SEARCH.CREATE addresses ON JSON PREFIX 1 address: LANGUAGE turkish SCHEMA address TEXT NOSTEM description TEXT
```

</Tab>

</Tabs>

Finally, it is possible safely create an index only if it does not exist, using
the `EXISTOK` option.

<Tabs>

<Tab title="TypeScript">
```ts {5}
const cache = await redis.search.createIndex({
  name: "cache",
  dataType: "string",
  prefix: "cache:",
  existsOk: true,
  schema: s.object({
    content: s.string(),
  }),
});
```
</Tab>

<Tab title="Python">
```python {5}
cache = redis.search.create_index(
    name="cache",
    data_type="string",
    prefixes="cache:",
    exists_ok=True,
    schema={
        "content": "TEXT",
    },
)
```
</Tab>

<Tab title="Redis CLI">
```bash
# Safe creation with EXISTOK
SEARCH.CREATE cache ON STRING PREFIX 1 cache: EXISTSOK SCHEMA content TEXT
```
</Tab>

</Tabs>

For the schema definition of the index, see the [Schema Definition](./schema-definition) section.

### Getting an Index Client

The `redis.search.index()` method creates a client for an existing index without making a Redis call. This is useful when you want to query or manage an index that already exists, without the overhead of creating it.

<Tabs>

<Tab title="TypeScript">
```ts {5-6,23}
import { Redis, s } from "@upstash/redis";

const redis = Redis.fromEnv();

// Get a client for an existing index
const users = redis.search.index({ name: "users" });

// Query the index
const results = await users.query({
  filter: { name: "John" },
});

// With schema for type safety
const userSchema = s.object({
  name: s.string(),
  email: s.string(),
  age: s.number(),
});

// Note: The schema parameter provides TypeScript type safety
// for queries and results. It does not validate against the
// server-side index schema.
const typedUsers = redis.search.index({ name: "users", schema: userSchema });

// Now queries are type-safe
const typedResults = await typedUsers.query({
  filter: { name: "John" },
});

```
</Tab>

<Tab title="Python">
```python {5-6}
from upstash_redis import Redis

redis = Redis.from_env()

# Get a client for an existing index
users = redis.search.index(name="users")

# Query the index
results = users.query(filter={"name": "John"})
```
</Tab>

</Tabs>

This method is different from `redis.search.createIndex()` which:
* Creates a new index if it doesn't exist
* Makes a Redis call to create the index
* Returns an error if the index already exists (unless `EXISTOK` is used)

Use `redis.search.index()` when:
* The index already exists
* You want to avoid unnecessary Redis calls
* You're querying or managing an existing index

Use `redis.search.createIndex()` when:
* You need to create a new index
* You're setting up your application for the first time

### Describing an Index

The [`SEARCH.DESCRIBE`](/docs/redis/commands/search/search-describe) command returns detailed information about an index, or `null` if the index doesn't exist.

<Tabs>

<Tab title="TypeScript">
```ts
const description = await index.describe();
// Returns IndexDescription or null if the index doesn't exist
```

</Tab>

<Tab title="Python">
```python
description = index.describe()
# Returns IndexDescription or None if the index doesn't exist
```

</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.DESCRIBE index
```
</Tab>

</Tabs>

On response, the following information is returned:

| Field      | Description                              |
| ---------- | ---------------------------------------- |
| `name`     | Index name                               |
| `type`     | Data type (`STRING`, `HASH`, or `JSON`)  |
| `prefixes` | List of tracked key prefixes             |
| `language` | Stemming language                        |
| `schema`   | Field definitions with types and options |

### Listing Indexes

[`SEARCH.LISTINDEXES`](/docs/redis/commands/search/search-listindexes) lists all search indexes in the database, reporting each index's `name` and backing `type` (`HASH` or `JSON`). Optionally filter names with `MATCH` and paginate with `LIMIT` and `OFFSET`.

<Tabs>

<Tab title="Redis CLI">
```bash
SEARCH.LISTINDEXES
# Returns: [["name", "productIdx", "type", "HASH"], ["name", "blogIdx", "type", "JSON"]]

SEARCH.LISTINDEXES MATCH 'product*' LIMIT 10 OFFSET 0
```
</Tab>

</Tabs>

### Dropping an Index

The [`SEARCH.DROP`](/docs/redis/commands/search/search-drop) command removes an index and stops tracking associated keys.

Returns `1` if the index was dropped, or `0` if the index was not found.

<Tabs>

<Tab title="TypeScript">
```ts
const result = await index.drop();
// Returns 1 (dropped) or 0 (index not found)
```
</Tab>

<Tab title="Python">
```python
result = index.drop()
# Returns 1 (dropped) or 0 (index not found)
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.DROP index
```
</Tab>

</Tabs>

Note that, dropping an index only removes the search index. The underlying Redis keys are not affected.

### Waiting for Indexing

For adequate performance, index updates are batched and committed periodically. This means recent writes may not
immediately appear in search results. Use [`SEARCH.WAITINDEXING`](/docs/redis/commands/search/search-waitindexing) when you need to ensure queries reflect recent changes.

The `SEARCH.WAITINDEXING` command blocks until all pending index updates are processed and visible to queries.
For examples of changing, deleting, and expiring indexed documents, see [Document Updates](/docs/redis/search/document-updates).

We recommend **not to** call this command each time you perform a write operation on the index. For optimal indexing and
query performance, batch updates are necessary. This command is primarily useful in tests and CI pipelines
where you need to guarantee that writes are reflected in subsequent queries.

Returns `1` when indexing is complete, or `0` if the index was not found.

<Tabs>

<Tab title="TypeScript">
```ts
// Add new document
await redis.json.set("product:new", "$", { name: "New Product", price: 49.99 });

// Ensure it's searchable before querying
const result = await index.waitIndexing();
// Returns 1 (indexing complete) or 0 (index not found)

// Now the query will include the new product
const products = await index.query({
  filter: { name: "new" },
});

for (const product of products) {
  console.log(product);
}

```
</Tab>

<Tab title="Python">
```python
# Add new document
redis.json().set("product:new", "$", {"name": "New Product", "price": 49.99})

# Ensure it's searchable before querying
result = index.wait_indexing()
# Returns 1 (indexing complete) or 0 (index not found)

# Now the query will include the new product
products = index.query(filter={"name": "new"})

for product in products:
    print(product)
```
</Tab>

<Tab title="Redis CLI">
```bash
# Add new document
JSON.SET product:new $ '{"name": "New Product", "price": 49.99}'

# Ensure it's searchable before querying
SEARCH.WAITINDEXING products

# Now the query will include the new product
SEARCH.QUERY products '{"name": "new"}'
```

</Tab>

</Tabs>

# Introduction
Source: https://upstash.com/docs/redis/search/introduction

import { Catalog } from "/snippets/catalog.jsx";

Upstash Redis Search is our first extension beyond the official Redis spec. Using the Rust-based Tantivy under the hood, we provide an extremely fast way to search through Redis data.

<img />

* **Easy Integration**: Works with JSON, Hashes and Strings out of the box
* **Auto-Synchronization**: Once you created an index, all write operations are automatically tracked
* **Intuitive Query Language**: A type-safe JSON-based query syntax with boolean operators, fuzzy matching, phrase queries,
  regex support, and more.
* **Production-Ready Performance**: Built on Tantivy, a fast full-text search engine library written in Rust

## Next Steps

<CardGroup cols={2}>
  <Card
    title="Getting Started"
    icon="rocket"
    href="/redis/search/getting-started"
  >
    Create your first index and run a query in a few lines of code
  </Card>
  <Card
    title="Schema Definition"
    icon="table-columns"
    href="/redis/search/schema-definition"
  >
    Define the fields you want to index and how they are matched
  </Card>
  <Card
    title="Querying"
    icon="magnifying-glass"
    href="/redis/search/querying"
  >
    Learn the JSON-based query language with filters and operators
  </Card>
  <Card
    title="Recipes"
    icon="book-open"
    href="/redis/search/recipes/overview"
  >
    Complete, real-world examples you can adapt to your own use cases
  </Card>
</CardGroup>

## Demos

<Catalog product="redis" type="demo" search cols={3} />

# $boost
Source: https://upstash.com/docs/redis/search/query-operators/boolean-operators/boost

The `$boost` operator adjusts the score contribution of a boolean clause.
Use it to fine-tune how much weight specific conditions have in the overall relevance ranking.

By default, all matching conditions contribute equally to a document's relevance score.
The `$boost` operator multiplies a clause's score contribution by the specified factor:

* **Values greater than 1** increase importance (e.g., `$boost: 5.0` makes the clause 5x more important)
* **Values between 0 and 1** decrease importance
* **Negative values** demote matches, pushing them lower in results

The most common use case is boosting specific `$should` conditions to prioritize certain matches:

```ts
{
  $must: { category: "electronics" },
  $should: [
    { description: "premium", $boost: 10.0 },  // High priority
    { description: "featured", $boost: 5.0 },  // Medium priority
    { description: "sale" }                    // Normal priority (default boost: 1.0)
  ]
}
```

Documents matching "premium" rank significantly higher than those matching only "sale".

Negative boost values demote matches without excluding them entirely.
This is useful when you want to push certain results lower without filtering them out:

```ts
{
  $must: { category: "electronics" },
  $should: {
    description: "budget",
    $boost: -2.0  // Push budget items lower in results
  }
}
```

Unlike `$mustNot`, which excludes documents entirely, negative boosting keeps documents in results but ranks them lower.

### Examples

<Tabs>

<Tab title="TypeScript">
```ts
// Prioritize wireless and in-stock items
await products.query({
  filter: {
    $must: {
      name: "headphones",
    },
    $should: {
      description: "wireless",
      inStock: true,
      $boost: 5.0,
    },
  },
});

// Demote budget items without excluding them
await products.query({
  filter: {
    $must: {
      category: "electronics",
    },
    $should: {
      description: "budget",
      $boost: -1.0,
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
# Prioritize wireless and in-stock items
products.query(filter={
    "$must": {"name": "headphones"},
    "$should": {"description": "wireless", "inStock": True, "$boost": 5.0},
})

# Demote budget items without excluding them
products.query(filter={
    "$must": {"category": "electronics"},
    "$should": {"description": "budget", "$boost": -1.0},
})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Prioritize wireless and in-stock items
SEARCH.QUERY products '{"$must": {"name": "headphones"}, "$should": {"description": "wireless", "inStock": true, "$boost": 5.0}}'

# Demote budget items without excluding them
SEARCH.QUERY products '{"$must": {"category": "electronics"}, "$should": {"description": "budget", "$boost": -1.0}}'
```
</Tab>

</Tabs>

# $must
Source: https://upstash.com/docs/redis/search/query-operators/boolean-operators/must

The `$must` operator requires all specified conditions to match.
Documents are only included in results if they satisfy every condition within the `$must` clause.
This implements logical AND behavior.

When you specify multiple field conditions at the top level of a query without any boolean operator,
they are implicitly wrapped in a `$must`. These queries are equivalent:

```ts
// Implicit must
{ name: "headphones", inStock: true }

// Explicit $must
{ $must: { name: "headphones", inStock: true } }
```

### Syntax Options

The `$must` operator accepts either an object or an array:

* **Object syntax**: Each key-value pair is a condition that must match
* **Array syntax**: Each element is a separate condition object that must match

Array syntax is useful when you have multiple conditions on the same field
or when building queries programmatically.

### Examples

<Tabs>

<Tab title="TypeScript">
```ts
// Explicit $must
await products.query({
  filter: {
    $must: [{ name: "headphones" }, { inStock: true }],
  },
});

// Equivalent implicit form
await products.query({
  filter: {
    name: "headphones",
    inStock: true,
  },
});

// $must with object syntax
await products.query({
  filter: {
    $must: {
      name: "headphones",
      inStock: true,
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
# Explicit $must
products.query(filter={"$must": [{"name": "headphones"}, {"inStock": True}]})

# Equivalent implicit form
products.query(filter={"name": "headphones", "inStock": True})

# $must with object syntax
products.query(filter={"$must": {"name": "headphones", "inStock": True}})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Explicit $must
SEARCH.QUERY products '{"$must": [{"name": "headphones"}, {"inStock": true}]}'

# Equivalent implicit form
SEARCH.QUERY products '{"name": "headphones", "inStock": true}'

# $must with object syntax
SEARCH.QUERY products '{"$must": {"name": "headphones", "inStock": true}}'
```
</Tab>

</Tabs>

# $mustNot
Source: https://upstash.com/docs/redis/search/query-operators/boolean-operators/must-not

The `$mustNot` operator excludes documents that match any of the specified conditions.
It acts as a filter, removing matching documents from the result set.

The `$mustNot` operator only filters results—it never adds documents to the result set.
This means it must be combined with `$must` or `$should` to define which documents to search.

A query with only `$mustNot` returns no results because there is no base set to filter:

```ts
// This returns NO results - nothing to filter
{ $mustNot: { category: "electronics" } }

// This works - $must provides the base set, $mustNot filters it
{ $must: { inStock: true }, $mustNot: { category: "electronics" } }
```

### Excluding Multiple Conditions

When `$mustNot` contains multiple conditions (via array or object), documents matching ANY of those conditions are excluded.
This is effectively an OR within the exclusion:

```ts
// Exclude documents that match category="generic" OR price > 500
{ $mustNot: [{ category: "generic" }, { price: { $gt: 500 } }] }
```

### Examples

<Tabs>

<Tab title="TypeScript">
```ts
// Exclude out-of-stock items
await products.query({
  filter: {
    $must: {
      category: "electronics",
    },
    $mustNot: {
      inStock: false,
    },
  },
});

// Exclude multiple conditions
await products.query({
  filter: {
    $must: {
      name: "headphones",
    },
    $mustNot: [{ category: "generic" }, { price: { $gt: 500 } }],
  },
});
```
</Tab>

<Tab title="Python">
```python
# Exclude out-of-stock items
products.query(filter={"$must": {"category": "electronics"}, "$mustNot": {"inStock": False}})

# Exclude multiple conditions
products.query(filter={
    "$must": {"name": "headphones"},
    "$mustNot": [{"category": "generic"}, {"price": {"$gt": 500}}],
})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Exclude out-of-stock items
SEARCH.QUERY products '{"$must": {"category": "electronics"}, "$mustNot": {"inStock": false}}'

# Exclude multiple conditions
SEARCH.QUERY products '{"$must": {"name": "headphones"}, "$mustNot": [{"category": "generic"}, {"price": {"$gt": 500}}]}'
```
</Tab>

</Tabs>

# Overview
Source: https://upstash.com/docs/redis/search/query-operators/boolean-operators/overview

Boolean operators combine multiple conditions to build complex queries.
They control how individual field conditions are logically combined to determine which documents match.

### Available Operators

| Operator | Description |
|----------|-------------|
| [`$must`](./must) | All conditions must match |
| [`$should`](./should) | At least one condition should match, or acts as a score booster when combined with `$must` |
| [`$mustNot`](./must-not) | Excludes documents matching any condition |
| [`$boost`](./boost) | Adjusts the score contribution of a boolean clause |

### How Boolean Operators Work Together

Boolean operators can be combined in a single query to express complex logic.
The operators work together as follows:

1. **`$must`** defines the required conditions. Documents must match ALL of these.
2. **`$should`** adds optional conditions:
   * When used alone: documents must match at least one condition
   * When combined with `$must`: conditions become optional score boosters
3. **`$mustNot`** filters out unwanted documents from the result set.

### Combining Operators

Here's an example that uses all three operators together:

<Tabs>

<Tab title="TypeScript">
```ts
// Find in-stock electronics, preferring wireless products, excluding budget items
await products.query({
  filter: {
    $must: {
      category: "electronics",
      inStock: true,
    },
    $should: [
      { name: "wireless" },
      { description: "bluetooth" },
    ],
    $mustNot: {
      description: "budget",
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
# Find in-stock electronics, preferring wireless products, excluding budget items
products.query(filter={
    "$must": {"category": "electronics", "inStock": True},
    "$should": [{"name": "wireless"}, {"description": "bluetooth"}],
    "$mustNot": {"description": "budget"},
})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Find in-stock electronics, preferring wireless products, excluding budget items
SEARCH.QUERY products '{"$must": {"category": "electronics", "inStock": true}, "$should": [{"name": "wireless"}, {"description": "bluetooth"}], "$mustNot": {"description": "budget"}}'
```
</Tab>

</Tabs>

This query:
1. **Requires** documents to be in the "electronics" category AND in stock
2. **Boosts** documents that mention "wireless" or "bluetooth" (but doesn't require them)
3. **Excludes** any documents containing "budget" in the description

### Nesting Boolean Operators

Boolean operators can be nested to create more complex logic:

<Tabs>

<Tab title="TypeScript">
```ts
// Find products that are either (premium electronics) OR (discounted sports items)
await products.query({
  filter: {
    $should: [
      {
        $must: {
          category: "electronics",
          description: "premium",
        },
      },
      {
        $must: {
          category: "sports",
          price: { $lt: 50 },
        },
      },
    ],
  },
});
```
</Tab>

<Tab title="Python">
```python
# Find products that are either (premium electronics) OR (discounted sports items)
products.query(filter={
    "$should": [
        {"$must": {"category": "electronics", "description": "premium"}},
        {"$must": {"category": "sports", "price": {"$lt": 50}}},
    ],
})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Find products that are either (premium electronics) OR (discounted sports items)
SEARCH.QUERY products '{"$should": [{"$must": {"category": "electronics", "description": "premium"}}, {"$must": {"category": "sports", "price": {"$lt": 50}}}]}'
```
</Tab>

</Tabs>

# $should
Source: https://upstash.com/docs/redis/search/query-operators/boolean-operators/should

The `$should` operator specifies conditions where at least one should match.
Its behavior changes depending on whether it's used alone or combined with `$must`.

When `$should` is the only boolean operator in a query, it acts as a logical OR.
Documents must match at least one of the conditions to be included in results:

```ts
// Match documents in electronics OR sports category
{ $should: [{ category: "electronics" }, { category: "sports" }] }
```

Documents matching more conditions score higher than those matching fewer.

When `$should` is combined with `$must`, the `$should` conditions become optional score boosters.
Documents are not required to match the `$should` conditions, but those that do receive higher relevance scores:

```ts
{
  $must: { category: "electronics" },    // Required: must be electronics
  $should: { description: "premium" }    // Optional: boosts score if present
}
```

This is useful for influencing result ranking without restricting the result set.

When multiple `$should` conditions are specified, each matching condition adds to the document's score.
Documents matching more conditions rank higher:

```ts
{
  $must: { category: "electronics" },
  $should: [
    { name: "wireless" },       // +score if matches
    { description: "bluetooth" }, // +score if matches
    { description: "premium" }    // +score if matches
  ]
}
```

A document matching all three `$should` conditions scores higher than one matching only one.

### Syntax Options

The `$should` operator accepts either an object or an array:

* **Object syntax**: Each key-value pair is a condition that should match
* **Array syntax**: Each element is a separate condition object that should match

Array syntax is useful when you have multiple conditions on the same field
or when building queries programmatically.

### Examples

<Tabs>

<Tab title="TypeScript">
```ts
// Match either condition
await products.query({
  filter: {
    $should: [{ category: "electronics" }, { category: "sports" }],
  },
});

// Optional boost: "wireless" is required, "premium" boosts score if present
await products.query({
  filter: {
    $must: {
      name: "wireless",
    },
    $should: {
      description: "premium",
    },
  },
});

// Multiple optional boosters
await products.query({
  filter: {
    $must: {
      category: "electronics",
    },
    $should: [
      { name: "wireless" },
      { description: "bluetooth" },
      { description: "premium" },
    ],
  },
});
```
</Tab>

<Tab title="Python">
```python
# Match either condition
products.query(filter={"$should": [{"category": "electronics"}, {"category": "sports"}]})

# Optional boost: "wireless" is required, "premium" boosts score if present
products.query(filter={"$must": {"name": "wireless"}, "$should": {"description": "premium"}})

# Multiple optional boosters
products.query(filter={
    "$must": {"category": "electronics"},
    "$should": [
        {"name": "wireless"},
        {"description": "bluetooth"},
        {"description": "premium"},
    ],
})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Match either condition
SEARCH.QUERY products '{"$should": [{"category": "electronics"}, {"category": "sports"}]}'

# Optional boost: "wireless" is required, "premium" boosts score if present
SEARCH.QUERY products '{"$must": {"name": "wireless"}, "$should": {"description": "premium"}}'

# Multiple optional boosters
SEARCH.QUERY products '{"$must": {"category": "electronics"}, "$should": [{"name": "wireless"}, {"description": "bluetooth"}, {"description": "premium"}]}'
```
</Tab>

</Tabs>

# $boost
Source: https://upstash.com/docs/redis/search/query-operators/field-operators/boost

The `$boost` operator adjusts the relevance score contribution of a field match.
This allows you to prioritize certain matches over others in the result ranking.

### How Boosting Works

Search results are ordered by a relevance score that reflects how well each document matches the query.
By default, all matching conditions contribute equally to this score.
The `$boost` operator multiplies a match's score contribution by the specified factor.

* **Positive values greater than 1** increase the match's importance (e.g., `$boost: 2.0` doubles the contribution)
* **Values between 0 and 1** decrease the match's importance
* **Negative values** demote matches, pushing them lower in results

### Use Cases

* **Prioritize premium content:** Boost matches in title fields over body text
* **Promote featured items:** Give higher scores to promoted products
* **Demote less relevant matches:** Use negative boosts to push certain matches down

### Compatibility

The `$boost` operator can be used with any field type since it modifies the score rather than the matching behavior.

| Field Type | Supported |
|------------|-----------|
| TEXT | Yes |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | Yes |
| KEYWORD | Yes |
| FACET | Yes |

### Examples

<Tabs>

<Tab title="TypeScript">
```ts
// Boost matches in $should clauses to prioritize certain terms
await products.query({
  filter: {
    $must: {
      inStock: true,
    },
    $should: [
      { description: "premium", $boost: 10.0 },
      { description: "quality", $boost: 5.0 },
    ],
  },
});

// Demote budget items with negative boost
await products.query({
  filter: {
    $must: {
      category: "electronics",
    },
    $should: [
      { name: "featured", $boost: 5.0 },
      { description: "budget", $boost: -2.0 },
    ],
  },
});
```
</Tab>

<Tab title="Python">
```python
# Boost matches in $should clauses to prioritize certain terms
products.query(filter={
    "$must": {"inStock": True},
    "$should": [
        {"description": "premium", "$boost": 10.0},
        {"description": "quality", "$boost": 5.0},
    ],
})

# Demote budget items with negative boost
products.query(filter={
    "$must": {"category": "electronics"},
    "$should": [
        {"name": "featured", "$boost": 5.0},
        {"description": "budget", "$boost": -2.0},
    ],
})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Boost matches in $should clauses to prioritize certain terms
SEARCH.QUERY products '{"$must": {"inStock": true}, "$should": [{"description": "premium", "$boost": 10.0}, {"description": "quality", "$boost": 5.0}]}'

# Demote budget items with negative boost
SEARCH.QUERY products '{"$must": {"category": "electronics"}, "$should": [{"name": "featured", "$boost": 5.0}, {"description": "budget", "$boost": -2.0}]}'
```
</Tab>

</Tabs>

# $eq
Source: https://upstash.com/docs/redis/search/query-operators/field-operators/eq

The `$eq` operator performs explicit equality matching on a field.
While you can often omit `$eq` and pass values directly (e.g., `{ price: 199.99 }`),
using `$eq` explicitly makes your intent clear and is required when combining with other operators.

For **text fields**, `$eq` performs a term search for single words.
For multi-word values, it behaves like a phrase query, requiring the words to appear adjacent and in order.
For **numeric fields**, it matches the exact value.
For **boolean fields**, it matches `true` or `false`.
For **date fields**, it matches the exact timestamp.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | Yes |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | Yes |
| KEYWORD | Yes |
| FACET | Yes |

### Examples

<Tabs>

<Tab title="TypeScript">
```ts
// Text field
await products.query({
  filter: {
    name: { $eq: "wireless headphones" },
  },
});

// Numeric field
await products.query({
  filter: {
    price: { $eq: 199.99 },
  },
});

// Boolean field
await products.query({
  filter: {
    inStock: { $eq: true },
  },
});

// Date field
await users.query({
  filter: {
    createdAt: { $eq: "2024-01-15T00:00:00Z" },
  },
});
```
</Tab>

<Tab title="Python">
```python
# Text field
products.query(filter={"name": {"$eq": "wireless headphones"}})

# Numeric field
products.query(filter={"price": {"$eq": 199.99}})

# Boolean field
products.query(filter={"inStock": {"$eq": True}})

# Date field
users.query(filter={"createdAt": {"$eq": "2024-01-15T00:00:00Z"}})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Text field
SEARCH.QUERY products '{"name": {"$eq": "wireless headphones"}}'

# Numeric field
SEARCH.QUERY products '{"price": {"$eq": 199.99}}'

# Boolean field
SEARCH.QUERY products '{"inStock": {"$eq": true}}'

# Date field
SEARCH.QUERY users '{"createdAt": {"$eq": "2024-01-15T00:00:00Z"}}'
```
</Tab>

</Tabs>

# $fuzzy
Source: https://upstash.com/docs/redis/search/query-operators/field-operators/fuzzy

The `$fuzzy` operator matches terms that are similar but not identical to the search term.
This is useful for handling typos, misspellings, and minor variations in user input.

### Levenshtein Distance

Fuzzy matching uses Levenshtein distance (also called edit distance) to measure similarity between terms.
The distance is the minimum number of single-character edits needed to transform one word into another.
These edits include:

* **Insertions:** Adding a character ("headphone" → "headphones")
* **Deletions:** Removing a character ("headphones" → "headphone")
* **Substitutions:** Replacing a character ("headphones" → "headphonez")

For example, the Levenshtein distance between "headphons" and "headphones" is 1 (one insertion needed).

### Distance Parameter

The `distance` parameter sets the maximum Levenshtein distance allowed for a match.
The default is 1, and the maximum allowed value is 2.

| Distance | Matches |
|----------|---------|
| 1 | Words with 1 edit (handles most single typos) |
| 2 | Words with up to 2 edits (handles more severe misspellings) |

Higher distances match more terms but may include unintended matches, so use distance 2 only when needed.

### Transposition Cost

A transposition swaps two adjacent characters (e.g., "teh" → "the").
By default, a transposition counts as 1 edit.
Setting `transpositionCostOne: false` counts transpositions as two edits (one deletion + one insertion) instead.

For example, searching for "haedphone" to find "headphone":
* With `transpositionCostOne: true`: Distance is 1 (swap counts as 1)
* Without `transpositionCostOne`: Distance is 2 (swap "ae" → "ea" costs 2)

This is useful when users commonly transpose characters while typing quickly.

### Prefix Matching

Setting `prefix: true` enables fuzzy prefix matching, which matches terms that start with a fuzzy
variation of the search term. This combines the typo tolerance of fuzzy matching with prefix
matching for incomplete words.

For example, searching for "headpho" with `prefix: true`:
* Matches "headphones" (prefix match)
* Matches "headphone" (prefix match)
* Matches "haedphones" with `transpositionCostOne: true` (fuzzy prefix, handles typo + incomplete word)

This is particularly useful for search-as-you-type autocomplete where users may have typos
in partially typed words.

### Multiple Words

When you provide multiple words to `$fuzzy`, each word is matched with fuzzy tolerance and combined using
a boolean [`$must`](../boolean-operators/must) query.
This means all terms must match (with their respective fuzzy tolerance) for a document to be returned.

For example, searching for "wireles headphons" will match documents containing both "wireless" and "headphones", even with the typos.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | Yes |
| U64/I64/F64 | No |
| DATE | No |
| BOOL | No |
| KEYWORD | No |
| FACET | No |

### Examples

<Tabs>

<Tab title="TypeScript">
```ts
// Simple fuzzy (distance 1, handles single typos)
// Matches "headphone" even with the typo "headphon"
await products.query({
  filter: {
    name: { $fuzzy: "headphon" },
  },
});

// Custom distance for more tolerance
// "haedphone" is 2 edits away from "headphone" without taking transposition into account
await products.query({
  filter: {
    name: {
      $fuzzy: {
        value: "haedphone",
        distance: 2,
        transpositionCostOne: false,
      },
    },
  },
});

// Handle character transpositions efficiently
// "haedphone" has swapped "ae", with transpositionCostOne this is 1 edit
await products.query({
  filter: {
    name: {
      $fuzzy: {
        value: "haedphone",
        distance: 1,
        transpositionCostOne: true,
      },
    },
  },
});

// Combine prefix with transposition for robust autocomplete
// Handles both typos and incomplete input like "haedpho" → "headphones"
await products.query({
  filter: {
    name: {
      $fuzzy: {
        value: "haedpho",
        prefix: true,
      },
    },
  },
});

// Multiple words - matches documents containing both terms (with fuzzy tolerance)
// Matches "wireless headphones" even with typos in both words
await products.query({
  filter: {
    name: { $fuzzy: "wireles headphons" },
  },
});
```
</Tab>

<Tab title="Python">
```python
# Simple fuzzy (distance 1, handles single typos)
products.query(filter={"name": {"$fuzzy": "headphon"}})

# Custom distance for more tolerance
products.query(filter={"name": {"$fuzzy": {"value": "haedphone", "distance": 2, "transpositionCostOne": False}}})

# Handle character transpositions efficiently
products.query(filter={"name": {"$fuzzy": {"value": "haedphone", "distance": 1, "transpositionCostOne": True}}})

# Combine prefix with transposition for robust autocomplete
products.query(filter={"name": {"$fuzzy": {"value": "haedpho", "prefix": True}}})

# Multiple words - matches documents containing both terms (with fuzzy tolerance)
products.query(filter={"name": {"$fuzzy": "wireles headphons"}})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Simple fuzzy (distance 1, handles single typos)
SEARCH.QUERY products '{"name": {"$fuzzy": "headphon"}}'

# Custom distance for more tolerance
SEARCH.QUERY products '{"name": {"$fuzzy": {"value": "haedphone", "distance": 2, "transpositionCostOne": false}}}'

# Handle character transpositions efficiently
SEARCH.QUERY products '{"name": {"$fuzzy": {"value": "haedphone", "distance": 1, "transpositionCostOne": true}}}'

# Combine prefix with transposition for robust autocomplete
SEARCH.QUERY products '{"name": {"$fuzzy": {"value": "haedpho", "prefix": true, "transpositionCostOne": true}}}'

# Multiple words - matches documents containing both terms (with fuzzy tolerance)
SEARCH.QUERY products '{"name": {"$fuzzy": "wireles headphons"}}'
```
</Tab>

</Tabs>

# $in
Source: https://upstash.com/docs/redis/search/query-operators/field-operators/in

The `$in` operator matches documents where the field value equals any one of the specified values.
This is useful when you want to filter by multiple acceptable values without writing separate conditions.

The operator takes an array of values and returns documents where the field matches at least one value in the array.
This is equivalent to combining multiple `$eq` conditions with a logical OR.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | Yes |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | Yes |
| KEYWORD | Yes |
| FACET | Yes |

### Examples

<Tabs>

<Tab title="TypeScript">
```ts
// Match any of several categories
await products.query({
  filter: {
    category: {
      $in: ["electronics", "accessories", "audio"],
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
# Match any of several categories
products.query(filter={"category": {"$in": ["electronics", "accessories", "audio"]}})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Match any of several categories
SEARCH.QUERY products '{"category": {"$in": ["electronics", "accessories", "audio"]}}'
```
</Tab>

</Tabs>

# Overview
Source: https://upstash.com/docs/redis/search/query-operators/field-operators/overview

Field operators provide precise control over how individual fields are matched.
Use these when simple value matching doesn't meet your needs.

### Quick Reference

| Operator | Supported Field Types | Description | Example |
|---|---|---|---|
| [`$smart`](./smart-matching) | all | Intelligent multi-stage matching (default behavior) | `{"name": {"$smart": "wireless"}}` |
| [`$eq`](./eq) | all | Exact equality | `{"status": {"$eq": "active"}}` |
| [`$in`](./in) | all | Match any of multiple values | `{"status": {"$in": ["active", "pending"]}}` |
| [`$gt`](./range-operators), `$gte`, `$lt`, `$lte` | numeric, date, keyword | Range comparison | `{"price": {"$gt": 10, "$lt": 100}}` |
| [`$phrase`](./phrase) | text | Phrase matching with optional slop and prefix | `{"text": {"$phrase": {"query": "hello world", "slop": 1}}}` |
| [`$fuzzy`](./fuzzy) | text | Typo-tolerant matching (Levenshtein distance) | `{"name": {"$fuzzy": {"term": "wireles", "distance": 1}}}` |
| [`$regex`](./regex) | text | Regular expression pattern matching | `{"name": {"$regex": "wire.*"}}` |
| [`$boost`](./boost) | modifier (on any operator) | Adjust relevance score weight | `{"name": {"$eq": "wireless", "$boost": 2.0}}` |

# $phrase
Source: https://upstash.com/docs/redis/search/query-operators/field-operators/phrase

The `$phrase` operator matches documents where terms appear in a specific sequence.
Unlike a simple multi-term search that finds documents containing all terms anywhere,
phrase matching requires the terms to appear in the exact order specified.

### Simple Form

In its simplest form, `$phrase` takes a string value and requires the terms to appear adjacent to each other in order:

```
{ $phrase: "wireless headphones" }
```

This matches "wireless headphones" but not "wireless bluetooth headphones" or "headphones wireless".

### Slop Parameter

The `slop` parameter allows flexibility in phrase matching by permitting other words to appear between your search terms.
The slop value represents the maximum number of position moves allowed to match the phrase.

For example, with `slop: 2`:
* "wireless headphones" matches (0 moves needed)
* "wireless bluetooth headphones" matches (1 move: "headphones" shifted 1 position)
* "wireless bluetooth overhead headphones" matches (2 moves)
* "wireless bluetooth noise-cancelling headphones" does NOT match (requires 3 moves)

A slop of 0 requires exact adjacency (the default behavior).

### Prefix Parameter

The `prefix` parameter allows the last term to match as a prefix.
This is useful for autocomplete scenarios where users might not complete the final word:

```
description: { $phrase: { value: "wireless head", prefix: true } }
```

This matches "wireless headphones", "wireless headset", etc.

**Note:** You can use either `slop` or `prefix`, but not both in the same query.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | Yes |
| U64/I64/F64 | No |
| DATE | No |
| BOOL | No |
| KEYWORD | No |
| FACET | No |

### Examples

<Tabs>

<Tab title="TypeScript">
```ts
// Simple phrase (terms must be adjacent)
await products.query({
  filter: {
    description: {
      $phrase: "noise cancelling",
    },
  },
});

// With slop (allow terms between, not exact adjacency)
await products.query({
  filter: {
    description: {
      $phrase: {
        value: "wireless headphones",
        slop: 2,
      },
    },
  },
});

// Prefix matching (last term can be partial)
await products.query({
  filter: {
    name: {
      $phrase: {
        value: "wireless head",
        prefix: true,
      },
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
# Simple phrase (terms must be adjacent)
products.query(filter={"description": {"$phrase": "noise cancelling"}})

# With slop (allow terms between, not exact adjacency)
products.query(filter={"description": {"$phrase": {"value": "wireless headphones", "slop": 2}}})

# Prefix matching (last term can be partial)
products.query(filter={"name": {"$phrase": {"value": "wireless head", "prefix": True}}})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Simple phrase (terms must be adjacent)
SEARCH.QUERY products '{"description": {"$phrase": "noise cancelling"}}'

# With slop (allow terms between, not exact adjacency)
SEARCH.QUERY products '{"description": {"$phrase": {"value": "wireless headphones", "slop": 2}}}'

# Prefix matching (last term can be partial)
SEARCH.QUERY products '{"name": {"$phrase": {"value": "wireless head", "prefix": true}}}'
```
</Tab>

</Tabs>

# Range Operators
Source: https://upstash.com/docs/redis/search/query-operators/field-operators/range-operators

Range operators filter documents based on numeric or date field boundaries.
You can use them to find values within a range, above a threshold, or below a limit.

| Operator | Description | Example |
|----------|-------------|---------|
| `$gt` | Greater than (exclusive) | `price > 100` |
| `$gte` | Greater than or equal (inclusive) | `price >= 100` |
| `$lt` | Less than (exclusive) | `price < 200` |
| `$lte` | Less than or equal (inclusive) | `price <= 200` |

You can combine multiple range operators on the same field to create bounded ranges.
For example, `{ $gte: 100, $lte: 200 }` matches values from 100 to 200 inclusive.

For open-ended ranges, use a single operator.
For example, `{ $gt: 500 }` matches all values greater than 500 with no upper limit.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | No |
| U64/I64/F64 | Yes |
| DATE | Yes |
| BOOL | No |
| KEYWORD | Yes |
| FACET | No |

### Examples

<Tabs>

<Tab title="TypeScript">
```ts
// Price range
await products.query({
  filter: {
    price: { $gte: 100, $lte: 200 },
  },
});

// Open-ended range (no upper bound)
await products.query({
  filter: {
    price: { $gt: 500 },
  },
});

// Date range (no lower bound)
await users.query({
  filter: {
    createdAt: { $lt: "2024-02-01T00:00:00Z" },
  },
});
```
</Tab>

<Tab title="Python">
```python
# Price range
products.query(filter={"price": {"$gte": 100, "$lte": 200}})

# Open-ended range (no upper bound)
products.query(filter={"price": {"$gt": 500}})

# Date range (no lower bound)
users.query(filter={"createdAt": {"$lt": "2024-02-01T00:00:00Z"}})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Price range
SEARCH.QUERY products '{"price": {"$gte": 100, "$lte": 200}}'

# Open-ended range (no upper bound)
SEARCH.QUERY products '{"price": {"$gt": 500}}'

# Date range (no lower bound)
SEARCH.QUERY users '{"createdAt": {"$lt": "2024-02-01T00:00:00Z"}}'
```
</Tab>

</Tabs>

# $regex
Source: https://upstash.com/docs/redis/search/query-operators/field-operators/regex

The `$regex` operator matches documents where field terms match a regular expression pattern.
This is useful for flexible pattern matching when exact values are unknown or when you need to match variations of a term.

### How Regex Matching Works

Regex patterns are matched against individual tokenized terms, not the entire field value.
Text fields are tokenized (split into words) during indexing, so the regex is applied to each term separately.

For example, if a document contains "hello world" in a text field:
* `{ $regex: "hel.*" }` matches (matches the "hello" term)
* `{ $regex: "hello world.*" }` does NOT match (regex cannot span multiple terms)
* `{ $regex: "wor.*" }` matches (matches the "world" term)

For exact phrase matching across multiple words, use the [$phrase](./phrase) operator instead.

### Performance Considerations

* **Avoid leading wildcards:** Patterns like `".*suffix"` require scanning all terms in the index and are slow.
  Prefer patterns that start with literal characters.

### Stemming Behavior

On fields with stemming enabled (the default), regex operates on the stemmed form of terms.
For example, "running" might be stored as "run", so a regex pattern `"running.*"` would not match.

To use regex reliably, consider using `NOSTEM` on fields where you need exact pattern matching.
See [Text Field Options](../../schema-definition#text-field-options) for more details.

### Compatibility

| Field Type | Supported |
|------------|-----------|
| TEXT | Yes |
| U64/I64/F64 | No |
| DATE | No |
| BOOL | No |
| KEYWORD | No |
| FACET | No |

### Examples

<Tabs>

<Tab title="TypeScript">
```ts
// Match categories starting with "e"
await products.query({
  filter: {
    category: {
      $regex: "e.*",
    },
  },
});

// Match email domains (requires NOTOKENIZE field)
await users.query({
  filter: {
    email: {
      $regex: ".*@company\\.com",
    },
  },
});

// Match product codes with pattern
await products.query({
  filter: {
    sku: {
      $regex: "SKU-[0-9]{4}",
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
# Match categories starting with "e"
products.query(filter={"category": {"$regex": "e.*"}})

# Match email domains (requires NOTOKENIZE field)
users.query(filter={"email": {"$regex": ".*@company\\.com"}})

# Match product codes with pattern
products.query(filter={"sku": {"$regex": "SKU-[0-9]{4}"}})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Match categories starting with "e"
SEARCH.QUERY products '{"category": {"$regex": "e.*"}}'

# Match email domains (requires NOTOKENIZE field)
SEARCH.QUERY users '{"email": {"$regex": ".*@company\\.com"}}'

# Match product codes with pattern
SEARCH.QUERY products '{"sku": {"$regex": "SKU-[0-9]{4}"}}'
```
</Tab>

</Tabs>

# Smart Matching
Source: https://upstash.com/docs/redis/search/query-operators/field-operators/smart-matching

When you provide a value directly to a field (without explicit operators like `$phrase` or `$fuzzy`),
the search engine applies smart matching. The behavior depends on the field type:

* **Numeric, boolean, and date fields**: Smart matching performs an exact equality check.
* **Text fields**: Smart matching applies intelligent matching strategies to find the most relevant results, as described below.

### Text Field Matching

For text fields, the matching behavior varies based on the input format.

### Single-Word Values

For single-word searches, the engine runs multiple matching strategies and combines their scores:

1. **Exact term match** (high boost): Documents containing the exact token score highest.

2. **Fuzzy match** (no boost): Documents containing terms within Levenshtein distance 1
   (with transpositions counting as a single edit) are included.

3. **Fuzzy prefix match** (no boost): Documents containing terms that start with a fuzzy
   variation of the search term are included. This handles incomplete words during typing.

```
{ name: "tabletop" }
```

This query returns results in roughly this order:
* "**tabletop**" (exact match)
* "**tabeltop**" (fuzzy match, 1 edit away)
* "**tabeltopping**" (fuzzy prefix match, incomplete word with typos matches full term)

### Multi-Word Values

For multi-word searches, the engine runs multiple matching strategies simultaneously and combines
their scores to surface the most relevant results:

1. **Exact phrase match** (highest boost): Documents where all words appear adjacent and in order
   score highest. Searching for `wireless headphones` ranks documents containing
   "wireless headphones" as a phrase above those with the words scattered.

2. **Exact phrase match with slop** (medium boost): Documents where all the query terms appear in
   order but not necessarily adjacent, allowing for a small number of intervening words—receive a boost.
   Searching for wireless headphones would rank documents containing `wireless bluetooth headphones`
   above those where the words are far apart, but below an exact phrase match (`wireless headphones`).

3. **Terms match** (medium boost): Documents containing all or some of the search terms, regardless of
   position or order, receive a moderate score boost.

3. **Fuzzy matching** (no boost): Documents containing terms similar to the search terms
   (accounting for typos) are included with a lower score.

4. **Fuzzy prefix on last word** (no boost): The last word is also matched with fuzzy prefix,
   handling incomplete words during search-as-you-type scenarios.

```
{ description: "wireless headphon" }
```

This query returns results in roughly this order:
* "Premium **wireless headphones** with noise cancellation" (phrase match with fuzzy prefix on last word)
* "**Headphones** with **wireless** connectivity" (all terms present, different order)
* "**Wireles headphone** with long battery" (fuzzy match for typos)

### Double-Quoted Phrases

Wrapping your search value in double quotes forces exact phrase matching.
The words must appear adjacent and in the exact order specified.

```
{ description: "\"noise cancelling\"" }
```

This matches only documents containing "noise cancelling" as an exact phrase.
It will NOT match:
* "noise and cancelling" (words not adjacent)
* "cancelling noise" (wrong order)
* "noise-cancelling" (hyphenated, tokenized differently)

This is useful when you need precise matching without the fuzzy tolerance of smart matching.

### The `$smart` Operator

Smart matching is applied implicitly when you provide a plain value to a field. You can also invoke it explicitly using the `$smart` operator. The behavior is identical — `$smart` is useful when you want to be more explicit about your intent, or when you need to combine smart matching with other operators like [`$boost`](./boost).

<Tabs>

<Tab title="TypeScript">
```ts
// Explicit smart matching
await index.query({
  filter: { name: { $smart: "wireless headphones" } },
});

// Combine smart matching with $boost
await index.query({
  filter: {
    $and: [
      { name: { $smart: "wireless headphones", $boost: 2.0 } },
      { description: { $smart: "noise cancelling" } },
    ],
  },
});
```
</Tab>

<Tab title="Python">
```python
# Explicit smart matching
index.query(filter={"name": {"$smart": "wireless headphones"}})

# Combine smart matching with $boost
index.query(filter={
    "$and": [
        {"name": {"$smart": "wireless headphones", "$boost": 2.0}},
        {"description": {"$smart": "noise cancelling"}},
    ],
})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Explicit smart matching
SEARCH.QUERY products '{"name": {"$smart": "wireless headphones"}}'

# Combine smart matching with $boost
SEARCH.QUERY products '{"$and": [{"name": {"$smart": "wireless headphones", "$boost": 2.0}}, {"description": {"$smart": "noise cancelling"}}]}'
```
</Tab>

</Tabs>

### When to Use Explicit Operators

Smart matching works well for general search scenarios, but consider using explicit operators when you need:

* **Typo tolerance only**: Use [`$fuzzy`](./fuzzy) with specific distance settings
* **Phrase with gaps**: Use [`$phrase`](./phrase) with the `slop` parameter
* **Pattern matching**: Use [`$regex`](./regex) for regular expression patterns

# Queries
Source: https://upstash.com/docs/redis/search/querying

Queries are JSON strings that describe which documents to return. If the index doesn't exist, queries return `null`.

Use the [`SEARCH.QUERY`](/docs/redis/commands/search/search-query) command to run a query.

We recommend searching by field values directly because we automatically provide intelligent matching behavior out of the box:

<Tabs>

<Tab title="TypeScript">
```ts
// Basic search
await index.query({
  filter: { name: "headphones" },
});

// Search across multiple fields (implicit AND)
await index.query({
  filter: { name: "wireless", category: "electronics" },
});

// Search with exact values for non-text fields
await index.query({
  filter: { inStock: true, price: 199.99 },
});
```
</Tab>

<Tab title="Python">
```python
# Basic search
index.query(filter={"name": "headphones"})

# Search across multiple fields (implicit AND)
index.query(filter={"name": "wireless", "category": "electronics"})

# Search with exact values for non-text fields
index.query(filter={"inStock": True, "price": 199.99})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Search for a term in a specific field
SEARCH.QUERY products '{"name": "headphones"}'

# Search across multiple fields (implicit AND)
SEARCH.QUERY products '{"name": "wireless", "category": "electronics"}'

# Search with exact values for non-text fields
SEARCH.QUERY products '{"inStock": true, "price": 199.99}'
```
</Tab>

</Tabs>

***

### Response Format

The query response is an array of matching documents. Each document includes the Redis key, a relevance score, and the document content.

<Tabs>

<Tab title="TypeScript">
```ts
const results = await index.query({
  filter: { name: "headphones" },
});

// results is an array of objects:
// [
//   { key: "product:1", score: 5.23, data: { name: "Wireless Headphones", price: 99.99, ... } },
//   { key: "product:2", score: 3.11, data: { name: "Studio Headphones", price: 149.99, ... } },
// ]
```
</Tab>

<Tab title="Python">
```python
results = index.query(filter={"name": "headphones"})

# results is a list of objects:
# [
#   QueryResult(key="product:1", score=5.23, data={"name": "Wireless Headphones", "price": 99.99, ...}),
#   QueryResult(key="product:2", score=3.11, data={"name": "Studio Headphones", "price": 149.99, ...}),
# ]
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.QUERY products '{"name": "headphones"}'

# Response:
# [
#   ["product:1", "5.23", [["$", "{\"name\": \"Wireless Headphones\", \"price\": 99.99}"]]],
#   ["product:2", "3.11", [["$", "{\"name\": \"Studio Headphones\", \"price\": 149.99}"]]]
# ]
```
</Tab>

</Tabs>

When `select` / `NOCONTENT` is used, the response shape changes — see [Controlling Output](#3-controlling-output).

If no documents match, an empty array is returned. If the index doesn't exist, `null` is returned.

For exact syntax and a detailed breakdown of the raw response structure, see the [`SEARCH.QUERY` command reference](/docs/redis/commands/search/search-query).

***

### Smart Matching

When you provide a value directly to a field (without explicit operators),
we automatically apply [smart matching](./query-operators/field-operators/smart-matching).
For numeric, boolean, and date fields, this performs an exact equality check.
For text fields, it works like this:

* **Single-word values**: Performs a term search, matching the word against tokens in the field.
* **Multi-word values**: Combines phrase matching, term matching, and fuzzy matching with
  different boost weights to rank exact phrases highest while still finding partial matches.
* **Double-quoted phrases**: Forces exact phrase matching (e.g., `"\"noise cancelling\""` matches
  only those words adjacent and in order).

For more control, use explicit operators like [`$phrase`](./query-operators/field-operators/phrase),
or [`$fuzzy`](./query-operators/field-operators/fuzzy).

***

## Query Options

### 1. Pagination with Limit and Offset

Limit controls how many results to return. Offset controls how many results to skip. Together, they provide a way to paginate results.

| Parameter | Description | Default | Constraints |
|-----------|-------------|---------|-------------|
| `LIMIT` | Number of results to return | 10 | Must be between 1 and 1000 |
| `OFFSET` | Number of results to skip | 0 | Must be non-negative |

<Note>
`LIMIT` and `OFFSET` are separate keywords. This differs from RediSearch which uses the combined
`LIMIT <offset> <count>` syntax. Using `LIMIT 0 10` will return an error.
</Note>

<Tabs>

<Tab title="TypeScript">
```ts
// Page 1: first 10 results (with optional offset)
const page1 = await index.query({
  filter: { description: "wireless" },
  limit: 10,
});

// Page 2: results 11-20
const page2 = await index.query({
  filter: { description: "wireless" },
  limit: 10,
  offset: 10,
});

// Page 3: results 21-30
const page3 = await index.query({
  filter: { description: "wireless" },
  limit: 10,
  offset: 20,
});
```
</Tab>

<Tab title="Python">
```python
# Page 1: first 10 results
page1 = index.query(filter={"description": "wireless"}, limit=10)

# Page 2: results 11-20
page2 = index.query(filter={"description": "wireless"}, limit=10, offset=10)

# Page 3: results 21-30
page3 = index.query(filter={"description": "wireless"}, limit=10, offset=20)
```
</Tab>

<Tab title="Redis CLI">
```bash
# Page 1: first 10 results (with optional offset)
SEARCH.QUERY products '{"description": "wireless"}' LIMIT 10

# Page 2: results 11-20
SEARCH.QUERY products '{"description": "wireless"}' LIMIT 10 OFFSET 10

# Page 3: results 21-30
SEARCH.QUERY products '{"description": "wireless"}' LIMIT 10 OFFSET 20
```
</Tab>

</Tabs>

### 2. Sorting Results

Normally, search results are sorted in descending order of query relevance.

It is possible to override this, and sort the results by a certain field
in ascending or descending order.

Only fields defined as `.fast()` in the schema can be used as the sort field (enabled by default).

When using `orderBy`, the score in results reflects the sort field's value rather than relevance.

<Tabs>

<Tab title="TypeScript">
```ts
// Sort by price, cheapest first
await products.query({
  filter: { category: "electronics" },
  orderBy: { price: "ASC" },
});

// Sort by date, newest first
await articles.query({
  filter: { author: "john" },
  orderBy: { publishedAt: "DESC" },
});

// Sort by rating, highest first, which can be combined with LIMIT and OFFSET
await products.query({
  filter: { inStock: true },
  orderBy: { rating: "DESC" },
  limit: 5,
});
```
</Tab>

<Tab title="Python">
```python
# Sort by price, cheapest first
products.query(filter={"category": "electronics"}, order_by={"price": "ASC"})

# Sort by date, newest first
articles.query(filter={"author": "john"}, order_by={"publishedAt": "DESC"})

# Sort by rating, highest first
products.query(filter={"inStock": True}, order_by={"rating": "DESC"}, limit=5)
```
</Tab>

<Tab title="Redis CLI">
```bash
# Sort by price, cheapest first
SEARCH.QUERY products '{"category": "electronics"}' ORDERBY price ASC

# Sort by date, newest first
SEARCH.QUERY articles '{"author": "john"}' ORDERBY publishedAt DESC

# Sort by rating, highest first, which can be combined with LIMIT and OFFSET
SEARCH.QUERY products '{"inStock": true}' ORDERBY rating DESC LIMIT 5
```
</Tab>

</Tabs>

### 3. Controlling Output

By default, search results include document key, relevance score, and the contents of the document
(including the non-indexed fields).

For JSON and string indexes, that means the stored JSON objects as whole. For hash indexes, it means all fields and values.

<Tabs>

<Tab title="TypeScript">
```ts {3}
// Example: Return documents without content
await products.query({
  select: {},
  filter: { name: "headphones" },
});
```
</Tab>

<Tab title="Python">
```python
# Return documents without content
products.query(select={}, filter={"name": "headphones"})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Return only keys and scores
SEARCH.QUERY products '{"name": "headphones"}' NOCONTENT
```
</Tab>

</Tabs>

<Tabs>

<Tab title="TypeScript">
```ts {3}
// Example: Return only `name` and `price`
await products.query({
  select: { name: true, price: true },
  filter: { name: "headphones" },
});
```
</Tab>

<Tab title="Python">
```python
# Return only `name` and `price`
products.query(select={"name": True, "price": True}, filter={"name": "headphones"})
```
</Tab>

<Tab title="Redis CLI">
```bash
# Return specific fields only
SEARCH.QUERY products '{"name": "headphones"}' SELECT 2 name price
```
</Tab>

</Tabs>

<Note>
When using [aliased fields](/docs/redis/search/schema-definition#aliased-fields),
use the **actual document field name** (not the alias) when selecting fields to return.
This is because aliasing happens at the index level and does not modify the underlying documents.
</Note>

***

### 4. Score Function

Score function lets you tweak the relevance scores of search results using numeric field values from your documents.
This is useful when you want to incorporate signals like popularity, recency, or price into the final ranking.

Only `.fast()` fields of type `i64`, `u64`, or `f64` can be used with score function.

#### Field Values

Each `FIELDVALUE` entry references a numeric field and optionally configures
how its value is transformed before being applied to the score:

* **`MODIFIER`** (default: `none`) — A mathematical transformation applied to the field value before use:

| Modifier     | Description                                          |
| ------------ | ---------------------------------------------------- |
| `none`       | Use the field value as-is, with no transformation.   |
| `log`        | Common logarithm (log base 10).                      |
| `log1p`      | Common logarithm of `1 + value`.                     |
| `log2p`      | Common logarithm of `2 + value`.                     |
| `ln`         | Natural logarithm (log base e).                      |
| `ln1p`       | Natural logarithm of `1 + value`.                    |
| `ln2p`       | Natural logarithm of `2 + value`.                    |
| `square`     | Square of the value (`value²`).                      |
| `sqrt`       | Square root of the value.                            |
| `reciprocal` | Reciprocal of the value (`1 / value`).               |

* **`FACTOR`** (default: `1.0`) — A float multiplier applied **after** the modifier transformation.
* **`MISSING`** (default: `0.0`) — A float fallback value used when:
  * The field value is missing from the document.
  * The field value is invalid for the chosen modifier (e.g., a negative value with `log`).

  If both the field value and the missing value are invalid for the modifier, the final contribution is `MISSING * FACTOR`.

#### Combine Mode

When multiple `FIELDVALUE` entries are specified, `COMBINEMODE` controls how their results are combined:

* **`multiply`** (default) — Multiply all field value results together.
* **`sum`** — Add all field value results together.

#### Score Mode

`SCOREMODE` controls how the combined field value result is applied to the original query relevance score:

* **`multiply`** (default) — Multiply the original score by the combined result.
* **`sum`** — Add the combined result to the original score.
* **`replace`** — Replace the original score with the combined result entirely.

<Tabs>

<Tab title="TypeScript">
```ts
// Boost results by a popularity field
await products.query({
  filter: { name: "headphones" },
  scoreFunc: "popularity",
});

// Use log1p modifier to dampen high popularity values, with a factor of 2
await products.query({
  filter: { name: "headphones" },
  scoreFunc: {
    field: "popularity",
    modifier: "log1p",
    factor: 2.0,
  },
});

// Fallback to 1.0 when the field is missing or invalid
await products.query({
  filter: { name: "headphones" },
  scoreFunc: {
    field: "popularity",
    modifier: "log1p",
    missing: 1.0,
  },
});

// Combine two field values: boost by popularity and recency
await products.query({
  filter: { name: "headphones" },
  scoreFunc: {
    fields: [
      { field: "popularity", modifier: "log1p" },
      { field: "recencyScore", modifier: "sqrt" },
    ],
    combineMode: "sum",
  },
});

// Replace the original score entirely with the combined result
await products.query({
  filter: { name: "headphones" },
  scoreFunc: {
    field: "rating",
    scoreMode: "replace",
  },
});
```
</Tab>

<Tab title="Python">
```python
# Boost results by a popularity field
products.query(filter={"name": "headphones"}, score_func="popularity")

# Use log1p modifier to dampen high popularity values, with a factor of 2
products.query(
    filter={"name": "headphones"},
    score_func={"field": "popularity", "modifier": "log1p", "factor": 2.0},
)

# Fallback to 1.0 when the field is missing or invalid
products.query(
    filter={"name": "headphones"},
    score_func={"field": "popularity", "modifier": "log1p", "missing": 1.0},
)

# Combine two field values: boost by popularity and recency
products.query(
    filter={"name": "headphones"},
    score_func={
        "fields": [
            {"field": "popularity", "modifier": "log1p"},
            {"field": "recencyScore", "modifier": "sqrt"},
        ],
        "combineMode": "sum",
    },
)

# Replace the original score entirely with the combined result
products.query(
    filter={"name": "headphones"},
    score_func={"field": "rating", "scoreMode": "replace"},
)
```
</Tab>

<Tab title="Redis CLI">
```bash
# Boost results by a popularity field
SEARCH.QUERY products '{"name": "headphones"}' SCOREFUNC FIELDVALUE popularity

# Use log1p modifier to dampen high popularity values, with a factor of 2
SEARCH.QUERY products '{"name": "headphones"}' SCOREFUNC FIELDVALUE popularity MODIFIER log1p FACTOR 2.0

# Fallback to 1.0 when the field is missing or invalid
SEARCH.QUERY products '{"name": "headphones"}' SCOREFUNC FIELDVALUE popularity MODIFIER log1p MISSING 1.0

# Combine two field values: boost by popularity and recency
SEARCH.QUERY products '{"name": "headphones"}' SCOREFUNC COMBINEMODE sum FIELDVALUE popularity MODIFIER log1p FIELDVALUE recencyScore MODIFIER sqrt

# Replace the original score entirely with the combined result
SEARCH.QUERY products '{"name": "headphones"}' SCOREFUNC SCOREMODE replace FIELDVALUE rating
```
</Tab>

</Tabs>

<Warning>
`SCOREFUNC` cannot be used together with `ORDERBY`. Since `ORDERBY` overrides the relevance
score with the sort field's value, combining it with score function is not supported.
</Warning>

***

### 5. Highlighting

Highlighting allows you to see why a document matched the query by marking the matching portions of the document's fields.

By default, `<em>` and `</em>` are used as the highlight tags.

<Tabs>

<Tab title="TypeScript">
```ts
// Highlight matching terms
await products.query({
  filter: { description: "wireless noise cancelling" },
  highlight: { fields: ["description"] },
});

// Custom open and close highlight tags
await products.query({
  filter: { description: "wireless" },
  highlight: { fields: ["description"], preTag: "!!", postTag: "**" },
});
```
</Tab>

<Tab title="Python">
```python
# Highlight matching terms
products.query(
    filter={"description": "wireless noise cancelling"},
    highlight={"fields": ["description"]},
)

# Custom open and close highlight tags
products.query(
    filter={"description": "wireless"},
    highlight={"fields": ["description"], "preTag": "!!", "postTag": "**"},
)
```
</Tab>

<Tab title="Redis CLI">
```bash
# Highlight matching terms
SEARCH.QUERY products '{"description": "wireless noise cancelling"}' HIGHLIGHT FIELDS 1 description

# Custom open and close highlight tags
SEARCH.QUERY products '{"description": "wireless"}' HIGHLIGHT FIELDS 1 description TAGS !! **
```
</Tab>

</Tabs>

Note that highlighting only works for operators that resolve to terms, such as term or phrase queries.

<Note>
When using [aliased fields](/docs/redis/search/schema-definition#aliased-fields),
use the **alias name** (not the actual document field name) when specifying fields to highlight.
The highlighting feature works with indexed field names, which are the aliases.
</Note>

- [Blog Search](https://upstash.com/docs/redis/search/recipes/blog-search.md)
- [E-commerce Search](https://upstash.com/docs/redis/search/recipes/e-commerce-search.md)
- [Overview](https://upstash.com/docs/redis/search/recipes/overview.md)
- [User Directory](https://upstash.com/docs/redis/search/recipes/user-directory.md)

# Schemas
Source: https://upstash.com/docs/redis/search/schema-definition

Every search index requires a schema that defines the structure of searchable documents. The schema allows for type-safety and allows us to optimize your data for very fast queries.

We provide a schema builder utility called `s` that makes it easy to define a schema.

```ts
import { Redis, s } from "@upstash/redis"
```

### Field Type Reference

| SDK Method | Redis CLI Type | Description | Supports FAST | Range Operators | Text Operators |
|---|---|---|---|---|---|
| `s.string()` | `TEXT` | Full-text searchable field with tokenization and stemming | No | No | `$smart`, `$phrase`, `$fuzzy`, `$regex` |
| `s.keyword()` | `KEYWORD` | Exact-match string (no tokenization) | Yes | Yes (`$gt`, `$gte`, `$lt`, `$lte`) | No |
| `s.number()` | `F64` (default), `U64`, `I64` | Numeric field | Yes | Yes | No |
| `s.date()` | `DATE` | Date/time field | Yes | Yes | No |
| `s.boolean()` | `BOOL` | Boolean field | Yes | No | No |
| `s.facet()` | `FACET` | Hierarchical path-based field | No | No | No (only `$eq`, `$in`) |

<Note>
In the TypeScript SDK, `s.number()` defaults to `F64`. You can specify `s.number("U64")` or
`s.number("I64")` for unsigned or signed 64-bit integers. `F64` fields are FAST by default.
</Note>

### FAST Fields

The `FAST` flag creates a columnar store for a field, enabling:
* **Sorting** with `ORDERBY` in queries
* **Score functions** with `SCOREFUNC FIELDVALUE`
* **Metric aggregations** (`$avg`, `$sum`, `$min`, `$max`, `$count`, etc.)

In the TypeScript SDK, numeric (`F64`), boolean, and date fields are FAST **by default**. You can
disable it with `.fast(false)`. In Redis CLI, you must explicitly add the `FAST` keyword after the
field type.

```bash
# Redis CLI — FAST must be explicit
SEARCH.CREATE products ON JSON PREFIX 1 product: SCHEMA name TEXT price F64 FAST rating F64 FAST
```

If you attempt to use `ORDERBY`, `SCOREFUNC`, or metric aggregations on a non-FAST field, you will get an error.

***

### Basic Usage

The schema builder provides methods for each field type:

```ts
const schema = s.object({
  name: s.string(),
  age: s.number(),
  createdAt: s.date(),
  active: s.boolean(),
  tag: s.keyword(),
  category: s.facet(),
})
```

The schema builder also supports chaining field options. We'll see what `noTokenize()` and `noStem()` are used for in the section below.

```ts {2,3}
const schema = s.object({
  sku: s.string().noTokenize(),
  brand: s.string().noStem(),
  price: s.number(),
})
```

### Nested Objects

The schema builder supports nested object structures:

```ts
const schema = s.object({
  title: s.string(),
  author: s.object({
    name: s.string(),
    email: s.string(),
  }),
  stats: s.object({
    views: s.number(),
    likes: s.number(),
  }),
})
```

### Where to use the Schema

We need the schema when creating or querying an index:

For the complete index creation syntax, see [`SEARCH.CREATE`](/docs/redis/commands/search/search-create).

```ts
import { Redis, s } from "@upstash/redis"

const redis = Redis.fromEnv()

const schema = s.object({
  name: s.string(),
  description: s.string(),
  category: s.string().noTokenize(),
  price: s.number("F64"),
  inStock: s.boolean(),
})

const products = await redis.search.createIndex({
  name: "products",
  dataType: "json",
  prefix: "product:",
  schema,
})
```

***

## Tokenization & Stemming

When you store text in a search index, it goes through two transformations: **Tokenization** and **Stemming**. By default, text fields are both tokenized and stemmed. Understanding these helps you configure fields correctly.

### Tokenization

Tokenization splits text into individual searchable words (tokens) by breaking on spaces and punctuation.

| Original Text        | Tokens                       |
| -------------------- | ---------------------------- |
| `"hello world"`      | `["hello", "world"]`         |
| `"user@example.com"` | `["user", "example", "com"]` |
| `"SKU-12345-BLK"`    | `["SKU", "12345", "BLK"]`    |

This is great for natural language because searching for "world" will match "hello world". But it breaks values that should stay together.

**When to disable tokenization** with `.noTokenize()`:

* Email addresses (`user@example.com`)
* URLs (`https://example.com/page`)
* Product codes and SKUs (`SKU-12345-BLK`)
* UUIDs (`550e8400-e29b-41d4-a716-446655440000`)
* Category slugs (`electronics/phones/android`)

```ts
const schema = s.object({
  title: s.string(),
  email: s.string().noTokenize(),
  sku: s.string().noTokenize(),
})
```

***

### Stemming

Stemming reduces words to their root form so different variations match the same search.

| Word                                   | Stemmed Form |
| -------------------------------------- | ------------ |
| `"running"`, `"runs"`, `"runner"`      | `"run"`      |
| `"studies"`, `"studying"`, `"studied"` | `"studi"`    |
| `"experiments"`, `"experimenting"`     | `"experi"`   |

This way, a user searching for "running shoes" will also find "run shoes" and "runner shoes".

**When to disable stemming** with `.noStem()`:

* Brand names (`Nike` shouldn't match `Nik`)
* Proper nouns and names (`Johnson` shouldn't become `John`)
* Technical terms (`React` shouldn't match `Reac`)
* When using regex patterns (stemmed text won't match your expected patterns)

```ts
const schema = s.object({
  description: s.string(),
  brand: s.string().noStem(),
  authorName: s.string().noStem(),
})
```

***

## Keyword Fields

The `KEYWORD` field type is for exact-match strings. Unlike `TEXT` fields, keywords are not tokenized or stemmed — the entire value is treated as a single token.

KEYWORD fields support numeric query operators (`$eq`, `$in`, `$gt`, `$gte`, `$lt`, `$lte`), which are not available on `TEXT` fields.

<Tabs>

<Tab title="TypeScript">
```ts
const schema = s.object({
  tag: s.keyword(),
})
```
</Tab>

<Tab title="Python">
```python
schema = {
    "tag": "KEYWORD",
}
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.CREATE idx ON JSON PREFIX 1 prefix: SCHEMA tag KEYWORD
```
</Tab>

</Tabs>

**When to use KEYWORD instead of TEXT:**

* When you need range operators (`$gt`, `$gte`, `$lt`, `$lte`) on string values
* When the entire string should be treated as a single unit (no word splitting)
* For tags, labels, status codes, or any string that should match exactly

***

## Facet Fields

The `FACET` field type is for hierarchical path-based faceted search. Values must be `/`-delimited paths starting with `/`.

FACET fields only support `$eq` and `$in` operators.
They are primarily used with the [`$facet` aggregation](/docs/redis/search/aggregation-operators/bucket-aggregations/facet)
to build category trees and faceted navigation.

<Tabs>

<Tab title="TypeScript">
```ts
const schema = s.object({
  category: s.facet(),
})
```
</Tab>

<Tab title="Python">
```python
schema = {
    "category": "FACET",
}
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.CREATE idx ON JSON PREFIX 1 prefix: SCHEMA category FACET
```
</Tab>

</Tabs>

**Example — querying facet fields:**

<Tabs>

<Tab title="TypeScript">
```ts
await index.query({
  filter: { category: { $eq: "/category/books/fiction" } },
});

await index.query({
  filter: {
    category: { $in: ["/category/books", "/category/electronics"] },
  },
});
```
</Tab>

<Tab title="Python">
```python
index.query(filter={"category": {"$eq": "/category/books/fiction"}})

index.query(filter={"category": {"$in": ["/category/books", "/category/electronics"]}})
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.QUERY products '{"category": {"$eq": "/category/books/fiction"}}'

SEARCH.QUERY products '{"category": {"$in": ["/category/books", "/category/electronics"]}}'
```
</Tab>

</Tabs>

***

## Aliased Fields

Aliased fields allow you to index the same document field multiple times with different settings,
or to create shorter names for complex nested paths.
Use the `FROM` keyword to specify which document field the alias points to.

<Tabs>

<Tab title="TypeScript">
```ts
import { Redis, s } from "@upstash/redis";

const redis = Redis.fromEnv();

const products = await redis.search.createIndex({
  name: "products",
  dataType: "json",
  prefix: "product:",
  schema: s.object({
    description: s.string(),
    descriptionExact: s.string().noStem().from("description"),
    authorName: s.string().from("metadata.author.displayName"),
  }),
});
```

</Tab>

<Tab title="Python">
```python
from upstash_redis import Redis

redis = Redis.from_env()

products = redis.search.create_index(
    name="products",
    data_type="json",
    prefix="product:",
    schema={
        "description": "TEXT",
        "descriptionExact": {"type": "TEXT", "nostem": True, "from": "description"},
        "authorName": {"type": "TEXT", "from": "metadata.author.displayName"},
    },
)
```

</Tab>

<Tab title="Redis CLI">
```bash
# Index 'description' twice with different settings
# Create a short alias for a deeply nested field
SEARCH.CREATE products ON JSON PREFIX 1 product: SCHEMA description TEXT descriptionExact TEXT FROM description NOSTEM authorName TEXT FROM metadata.author.displayName
```

</Tab>

</Tabs>

Common use cases for aliased fields:

* **Same field with different settings**: Index a text field both with and without stemming. Use the stemmed version for general searches and the non-stemmed version for exact matching or regex queries.
* **Shorter query paths**: Create concise aliases for deeply nested fields like `metadata.author.displayName` to simplify queries.

<Note>
When using aliased fields:
* Use the **alias name** in queries and highlighting (e.g., `descriptionExact`, `authorName`)
* Use the **actual field name** when selecting fields to return (e.g., `description`, `metadata.author.displayName`)

This is because aliasing happens at the index level and does not modify the underlying documents.

</Note>

***

## Non-Indexed Fields

Documents don't need to match the schema exactly:

* **Extra fields**: Fields in your document that aren't defined in the schema are simply ignored. They won't be indexed or searchable.
* **Missing fields**: If a document is missing a field defined in the schema, that document won't appear in search results that filter on the missing field.

***

## Schema Examples

**E-commerce product schema**

<Tabs>

<Tab title="TypeScript">

```ts
import { Redis, s } from "@upstash/redis"

const redis = Redis.fromEnv()

const products = await redis.search.createIndex({
  name: "products",
  dataType: "hash",
  prefix: "product:",
  schema: s.object({
    name: s.string(),
    sku: s.string().noTokenize(), // Exact-match SKU codes
    brand: s.string().noStem(), // Brand names without stemming
    description: s.string(),
    price: s.number("F64"), // Sortable (F64) price
    rating: s.number("F64"), // Sortable (F64) rating
    reviewCount: s.number("U64"),  // Non-sortable (U64) review count
    inStock: s.boolean(),
  }),
})
```

</Tab>

<Tab title="Python">
```python
from upstash_redis import Redis

redis = Redis.from_env()

products = redis.search.create_index(
    name="products",
    data_type="hash",
    prefix="product:",
    schema={
        "name": "TEXT",
        "sku": {"type": "TEXT", "notokenize": True},
        "brand": {"type": "TEXT", "nostem": True},
        "description": "TEXT",
        "price": "F64",
        "rating": "F64",
        "reviewCount": "U64",
        "inStock": "BOOL",
    },
)
```

</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.CREATE products ON HASH PREFIX 1 product: SCHEMA name TEXT sku TEXT NOTOKENIZE brand TEXT NOSTEM description TEXT price F64 FAST rating F64 FAST reviewCount U64 inStock BOOL FAST
```
</Tab>

</Tabs>

**User directory schema**

<Tabs>

<Tab title="TypeScript">
```ts
import { Redis, s } from "@upstash/redis";

const redis = Redis.fromEnv();

const users = await redis.search.createIndex({
  name: "users",
  dataType: "json",
  prefix: "user:",
  schema: s.object({
    username: s.string().noTokenize(),
    profile: s.object({
      displayName: s.string().noStem(),
      bio: s.string(),
      email: s.string().noTokenize(),
    }),
    createdAt: s.date().fast(),
    verified: s.boolean(),
  }),
});
```

</Tab>

<Tab title="Python">
```python
from upstash_redis import Redis

redis = Redis.from_env()

users = redis.search.create_index(
    name="users",
    data_type="json",
    prefix="user:",
    schema={
        "username": {"type": "TEXT", "notokenize": True},
        "profile.displayName": {"type": "TEXT", "nostem": True},
        "profile.bio": "TEXT",
        "profile.email": {"type": "TEXT", "notokenize": True},
        "createdAt": {"type": "DATE", "fast": True},
        "verified": "BOOL",
    },
)
```

</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.CREATE users ON JSON PREFIX 1 users: SCHEMA username TEXT NOTOKENIZE profile.displayName TEXT NOSTEM profile.bio TEXT contact.email TEXT NOTOKENIZE createdAt DATE FAST verified BOOL
```
</Tab>

</Tabs>

# Troubleshooting
Source: https://upstash.com/docs/redis/search/troubleshooting

Common errors and how to resolve them when working with Upstash Redis Search.

## Index Errors

### `ERR Index <name> already exists`

You tried to create an index that already exists.

**Fix:** Use the `existsOk` option (SDK) or `EXISTSOK` flag (CLI) to skip creation if an identical index already exists:

```bash
SEARCH.CREATE products ON JSON PREFIX 1 product: EXISTSOK SCHEMA name TEXT
```

Or drop the existing index first with `SEARCH.DROP <name>`.

If the existing index has a different schema/configuration, `EXISTSOK` still returns an error. In that case, drop and recreate the index (or use a different index name).

***

## Query Errors

### `ERR limit should be a positive number less than 1000`

The `LIMIT` value must be a positive integer. Valid range is `1` to `1000` (inclusive).

**Common causes:**
* Using `LIMIT 0` — the minimum is 1
* Using `LIMIT 1001` or greater — the maximum is 1000
* Using RediSearch-style `LIMIT <offset> <count>` syntax — Upstash uses separate `LIMIT` and `OFFSET` keywords

**Fix:**
```bash
# Correct
SEARCH.QUERY products '{}' LIMIT 10 OFFSET 20

# Incorrect (RediSearch style)
SEARCH.QUERY products '{}' LIMIT 20 10
```

### `ERR Unknown field operator: $not`

There is no `$not` operator. Use `$mustNot` for exclusion filtering:

```bash
# Correct
SEARCH.QUERY products '{"$mustNot": [{"status": "discontinued"}]}'

# Incorrect
SEARCH.QUERY products '{"status": {"$not": "discontinued"}}'
```

***

## Aggregation Errors

### Aggregation operator requires field to be FAST

```
Aggregation '<name>' operator '$avg' requires field '<field>' to be FAST
```

All metric aggregation operators (`$avg`, `$sum`, `$min`, `$max`, `$count`, `$cardinality`, `$stats`, `$extendedStats`, `$percentiles`) require the target field to be defined as `FAST` in the index schema.

**Fix:** Recreate the index with the field marked as `FAST`:

```bash
# Ensure 'price' has the FAST flag
SEARCH.CREATE products ON JSON PREFIX 1 product: SCHEMA name TEXT price F64 FAST
```

In the TypeScript SDK, numeric fields (`s.number("F64")`) and date fields (`s.date()`) are FAST by default. Boolean fields (`s.boolean()`) are also FAST by default. If you explicitly called `.fast(false)`, remove it.

### Missing required 'field' property

```
Aggregation '<name>' is missing required 'field' property
```

Every metric aggregation operator requires a `field` property pointing to the field to aggregate.

**Fix:** Pass `field` as an object property, not a bare string:

Correct:
```json
{"avg_price": {"$avg": {"field": "price"}}}
```

Incorrect — operator value is a bare string instead of an object:
```json
{"avg_price": {"$avg": "price"}}
```

### Aggregation must be a JSON object

```
Aggregation '<name>' must be a JSON object
```

The aggregation alias must map to an object containing an operator key (like `$avg`), not directly to a string or number.

**Fix:**

Correct:
```json
{"avg_price": {"$avg": {"field": "price"}}}
```

Incorrect — alias maps directly to an operator string:
```json
{"avg_price": "$avg"}
```

***

## Schema Errors

### Field type mismatches

If a document field's value doesn't match the schema type (e.g., a string value `"abc"` for a numeric field), that document is silently skipped during indexing for that field. It will not appear in queries that filter on the mismatched field.

### Missing document fields

If a document doesn't have a field defined in the schema, it simply won't match queries filtering on that field. No error is raised.

***

## Upstash Redis Search vs RediSearch

Upstash Redis Search uses `SEARCH.*` commands and is a separate implementation from the
open-source RediSearch module which uses `FT.*` commands. The two are **not** compatible.

| | Upstash Redis Search | RediSearch |
|---|---|---|
| Command prefix | `SEARCH.*` | `FT.*` |
| Engine | Tantivy (Rust) | RediSearch (C) |
| Query syntax | JSON-based filters | RediSearch query syntax |
| Pagination | `LIMIT <count> OFFSET <offset>` (separate keywords) | `LIMIT <offset> <count>` (combined) |
| Hosting | Serverless (Upstash) | Self-hosted or Redis Cloud |

If you are migrating from RediSearch, you will need to rewrite your queries to use the JSON filter syntax and `SEARCH.*` commands.

# Unexpected Increase in Command Count
Source: https://upstash.com/docs/redis/troubleshooting/command_count_increases_unexpectedly

### Symptom

You notice an increasing command count for your Redis database in the Upstash Console, even when there are no connected clients.

### Diagnosis

The Upstash Console interacts with your Redis database to provide its functionality, which can result in an increased command count. This behavior is normal and expected. Here's a breakdown of why this occurs:

1. **Data Browser functionality:**
   The Data Browser tab sends various commands to list and display your keys, including:
   * SCAN: To iterate through the keyspace
   * GET: To retrieve values for keys
   * TTL: To check the time-to-live for keys

2. **Rate Limiting check:**
   The Console checks if your database is being used for Rate Limiting. This involves sending EXISTS commands for rate limiting-related keys.

3. **Other Console features:**
   Additional features in the Console may send commands to your database to retrieve or display information.

### Verification

You can use the Monitor tab in the Upstash Console to observe which commands are being sent by the Console itself. This can help you distinguish between Console-generated commands and those from your application or other clients.
Also, Usage tab contains 'Top Commands Usage' graph which shows the exact command history.

### Conclusion

The increasing command count you're seeing is likely due to the Console's normal operations and should not be a cause for concern. These commands do not significantly impact your database's performance or your usage limits.

If you have any further questions or concerns about command usage, please don't hesitate to contact Upstash support.

# ERR DB capacity quota exceeded
Source: https://upstash.com/docs/redis/troubleshooting/db_capacity_quota_exceeded

### Symptom

The client gets an exception similar to:

```
ReplyError: ERR DB capacity quota exceeded
```

### Diagnosis

Your total database size exceeds the max data size limit of your current plan. When this limit is reached,
write requests may be rejected. Read and delete requests will not be affected.

### Solution-1

You can manually delete some entries to allow further writes. Additionally you
can consider setting TTL (expiration time) for your keys or enable
[eviction](../features/eviction) for your database.

### Solution-2

You can upgrade your database to Pro for higher limits.

# Error read ECONNRESET
Source: https://upstash.com/docs/redis/troubleshooting/econn_reset

### Symptom

The client can not connect to the database throwing an exception similar to:

```
[ioredis] Unhandled error event: Error: read ECONNRESET
    at TCP.onStreamRead (node:internal/stream_base_commons:211:20)
```

### Diagnosis

The server is TLS enabled but your connection (client) is not.

### Solution

Check your connection parameters and ensure you enable TLS.

If you are using a Redis URL then it should start with `rediss://`.

You can copy the correct client configuration from Upstash console clicking on
**Redis Connect** button.

# WRONGPASS invalid or missing auth token
Source: https://upstash.com/docs/redis/troubleshooting/http_unauthorized

### Symptom

The database rejects your request with an error similar to:

```
UpstashError: WRONGPASS invalid or missing auth token
```

### Diagnosis

The server rejects your request because the auth token is missing or invalid.

Most likely you have forgotten to set it in your environment variables, or you
are using a wrong token.

The connection password can only be used in traditional Redis clients. If you
want to connect over HTTP, you need to use the HTTP auth token.

### Solution

1. Check that you have set the `UPSTASH_REDIS_REST_TOKEN` in your environment
   variables and it is loaded correctly by your application at runtime.

2. Make sure you are using the correct HTTP auth token. You can copy the correct
   client configuration from the
   [Upstash console](https://console.upstash.com/redis) by copying the snippet
   from the `Connect` section -> `JavaScript` tab

![]()

Or select the `REST` tab in the `Connect` section and copy the
`UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from there.

![]()

# ERR max concurrent connections exceeded
Source: https://upstash.com/docs/redis/troubleshooting/max_concurrent_connections

### Symptom

New clients can not connect to the database throwing an exception similar to:

```
"message" : "[ioredis] Unhandled error event:
ReplyError: ERR max concurrent connections exceeded\r
at Object.onceWrapper (events.js:286:20)\r
at Socket.emit (events.js:203:15)\r    at Socket.EventEmitter.emit (domain.js:448:20)\r
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1093:10)\n"
```

### Diagnosis

You have reached the concurrent connection limit.

### Solution-1

You need to manage connections more efficiently. If you are using serverless
functions, you can create the Redis client inside the function and close the
connection when you are done with the database as below.

<Note>
  This solution may have a latency overhead (about 4 ms). See [the blog
  post](https://blog.upstash.com/serverless-database-connections) for more.
</Note>

```javascript
exports.handler = async (event) => {
  const client = new Redis(process.env.REDIS_URL);
  /*
    do stuff with redis
     */
  await client.quit();
  /*
  do other stuff
   */
  return {
    response: "response",
  };
};
```

### Solution-2

You can use [@upstash/redis](https://github.com/upstash/upstash-redis) client
which is REST based so it does not have any connection related problems.

### Solution-3

Please reach out to us via support@upstash.com if above does not help.

<Info>
  See [the blog post](https://blog.upstash.com/serverless-database-connections)
  about the database connections in serverless functions.
</Info>

# ERR max daily request limit exceeded
Source: https://upstash.com/docs/redis/troubleshooting/max_daily_request_limit

### Symptom

The client gets an exception similar to:

```
ReplyError: ERR max daily request limit exceeded
```

### Diagnosis

Your database exceeds the max daily request count limit.

### Solution-1

You can refactor your application to send less number of commands.

### Solution-2

You can upgrade your database to a paid plan, such as pay-as-you-go or a fixed plan
by entering a payment method. When you entered your credit card, your database will be upgraded
automatically.

See [here](../howto/upgrade-database) for more information.

# ERR max key size exceeded
Source: https://upstash.com/docs/redis/troubleshooting/max_key_size_exceeded

### Symptom

The client gets an exception similar to:

```
ReplyError: ERR max key size exceeded. Limit: X bytes, Actual: Z bytes
```

### Diagnosis

Size of the key in the request exceeds the max key size limit, which is `32Kb`.

### Solution

This is a hardcoded limit and cannot be configured per database. You should
reduce the key size.

# ERR max single record size exceeded
Source: https://upstash.com/docs/redis/troubleshooting/max_record_size_exceeded

### Symptom

The client gets an exception similar to:

```
ReplyError: ERR max single record size exceeded
```

### Diagnosis

An entry size exceeds the max record size limit which is `100Mb` for "Free" and
"Pay as you go" databases. You may reach this limit either by inserting a single
huge value or appending many small values to an entry. This entry can be a
String, List, Set, Hash etc. Read (`GET`, `LRANGE`, `HMGET`, `ZRANGE` etc) and
delete (`DEL`, `LPOP`, `HDEL`, `SREM` etc) requests will not be affected.

### Solution-1

You can split your data into smaller chunks and store them as separate entries
with different keys.

### Solution-2

You can upgrade your database to Pro as it has higher limits. Also
you can submit quota increase request in the console or contact
support@upstash.com about the options with higher max record size limit.

# ERR max request size exceeded
Source: https://upstash.com/docs/redis/troubleshooting/max_request_size_exceeded

### Symptom

The client gets an exception similar to:

```
ReplyError: ERR max request size exceeded
```

### Diagnosis

Your command exceeds the max request size which is `10MB` for "Free" and "Pay as
you go" databases.

### Solution-1

You can split your data into smaller chunks and send them in separate commands.

### Solution-2

You can upgrade your database to a higher plan, or apply a custom quota increase if you are on Pay-as-You-Go plan. Please reach out to support@upstash.com about the options with higher max request size limit.

<Note>
  max-request-size-limit is about the size of a single request. Your data
  structure (like list, set) can exceed the max request size limit without any
  problem. If you try to load all elements in the list with a single request
  then it can throw the max-request-size-limit exception.
</Note>

# ERR max requests limit exceeded
Source: https://upstash.com/docs/redis/troubleshooting/max_requests_limit

### Symptom

The client gets an exception similar to:

```
ReplyError: ERR max requests limit exceeded.
```

### Diagnosis

Your database exceeds the max monthly request count limit.

### Solution-1

You can refactor your application to send less number of commands.

### Solution-2

You can upgrade your database to a paid plan, such as pay-as-you-go or a fixed plan
by entering a payment method. When you entered your credit card, your database will be upgraded
automatically.

See [here](../howto/upgrade-database) for more information.

# NOAUTH Authentication Required
Source: https://upstash.com/docs/redis/troubleshooting/no_auth

### Symptom

The client can not connect to the database throwing an exception similar to:

```
[ioredis] Unhandled error event:
ReplyError: NOAUTH Authentication required
```

### Diagnosis

The server does not let you connect because the password is missing in your
connection parameters.

### Solution

Check your connection parameters and ensure they contain the password. If you
are using ioredis (Redis client) with a Redis URL, check the URL format. ioredis
requires a colon before the password. The format for IORedis - TLS enabled

```
rediss://:YOUR_PASSWORD@YOUR_ENDPOINT:YOUR_PORT
```

The format for IORedis - TLS disabled

```
redis://:YOUR_PASSWORD@YOUR_ENDPOINT:YOUR_PORT
```

You can copy the correct client configuration from Upstash console clicking on
**Redis Connect** button.

# Connecting with Read-Only Access
Source: https://upstash.com/docs/redis/troubleshooting/readonly_connection

### Symptom

You are trying to connect to your Upstash Redis database using a read-only token via a TCP client (such as `redis-cli`, `ioredis`, or `redis-py`), but you receive an authentication error similar to:

```
WRONGPASS invalid username-password pair or user is disabled
```

### Diagnosis

When you enable the **Read-Only Token** toggle in the [Upstash Console](https://console.upstash.com), the TCP connection strings update the username from `default` to `default_ro`. This change can be easy to miss.

If you copied the read-only password but are still connecting with the `default` username (or omitting the username entirely), authentication will fail because the read-only password is associated with the `default_ro` user.

### Solution 1: Use the `default_ro` Username

The simplest approach is to use the built-in `default_ro` user. When you enable the **Read-Only Token** in the console, the connection strings automatically switch to use this user.

Make sure your connection includes both the `default_ro` username **and** the read-only password:

**redis-cli:**

```bash
redis-cli --tls -u rediss://default_ro:READ_ONLY_PASSWORD@YOUR_ENDPOINT:YOUR_PORT
```

**Node.js (ioredis):**

```javascript
const Redis = require("ioredis");

const client = new Redis("rediss://default_ro:READ_ONLY_PASSWORD@YOUR_ENDPOINT:YOUR_PORT");
```

**Python (redis-py):**

```python
import redis

r = redis.Redis(
    host="YOUR_ENDPOINT",
    port=YOUR_PORT,
    username="default_ro",
    password="READ_ONLY_PASSWORD",
    ssl=True,
)
```

<Tip>
  The easiest way to get the correct connection string is to enable the **Read-Only Token** toggle in the console and copy the connection string directly. The username and password will be pre-filled for you.
</Tip>

### Solution 2: Create a Custom Read-Only ACL User

If you need more control over permissions, you can create a custom user with read-only access using [Redis ACL](https://redis.io/docs/latest/operate/oss_and_stack/management/security/acl/).

**1. Connect with your admin credentials:**

```bash
redis-cli --tls -u rediss://default:YOUR_PASSWORD@YOUR_ENDPOINT:YOUR_PORT
```

**2. Create a read-only user:**

```
ACL SETUSER myreadonlyuser on >somesecurepassword ~* &* +@read -@dangerous
```

This command:
* `on` — enables the user
* `>somesecurepassword` — sets the password
* `~*` — allows access to all keys
* `&*` — allows access to all pub/sub channels
* `+@read` — grants all read commands
* `-@dangerous` — revokes dangerous commands (such as `KEYS`, `SCAN`)

You can further restrict key access by replacing `~*` with a pattern like `~cache:*` to only allow reading keys that match that prefix.

**3. Connect with the new user:**

```bash
redis-cli --tls -u rediss://myreadonlyuser:somesecurepassword@YOUR_ENDPOINT:YOUR_PORT
```

If you want to use the REST API with your custom ACL user, you can generate a REST token for it. See [REST Token for ACL Users](/docs/redis/features/restapi#rest-token-for-acl-users) for details.

# ERR XReadGroup is cancelled
Source: https://upstash.com/docs/redis/troubleshooting/stream_pel_limit

### Symptom

The client gets an exception similar to:

```
ReplyError: ERR XReadGroup is cancelled. Pending Entries List limit per consumer is about to be reached. Limit: 1000, Current PEL size: 90, Requested Read: 20, Key: mstream, Group: group1, Consumer: consumer1.
```

### Diagnosis

Pending Entries List of the stream for the consumer is full. For each consumer
in a consumer group, there is a pending entries list. This list keeps the
messages that are delivered to a consumer but not yet acknowledged via
[XACK](/docs/redis/commands/streams/xack). This list is populated via
[XREADGROUP](/docs/redis/commands/streams/xreadgroup).

### Solution

Acknowledge the consumed messages via [XACK](/docs/redis/commands/streams/xack)
from the list of the associated group and consumer.

- [Agent Memory with Redis Search](https://upstash.com/docs/redis/tutorials/agent_memory.md): Build short-term and long-term memory for AI agents on Upstash Redis. Store working memory with TTLs and recall long-term memories with Redis Search full-text queries.
- [Deploy a Serverless API with AWS CDK and AWS Lambda](https://upstash.com/docs/redis/tutorials/api_with_cdk.md)
- [Autocomplete API with Serverless Redis](https://upstash.com/docs/redis/tutorials/auto_complete_with_serverless_redis.md)
- [Build Stateful Applications with AWS App Runner and Serverless Redis](https://upstash.com/docs/redis/tutorials/aws_app_runner_with_redis.md): This tutorial shows how to create a serverless and stateful application using AWS App Runner and Redis
- [Session Management on Google Cloud Run with Serverless Redis](https://upstash.com/docs/redis/tutorials/cloud_run_sessions.md): This tutorial shows how to manage user sessions on Google Cloud Run using Serverless Redis.
- [Cloudflare Workers with Websockets and Redis](https://upstash.com/docs/redis/tutorials/cloudflare_websockets_redis.md)
- [Use Redis in Cloudflare Workers](https://upstash.com/docs/redis/tutorials/cloudflare_workers_with_redis.md)
- [Backendless Coin Price List with GraphQL API, Serverless Redis and Next.JS](https://upstash.com/docs/redis/tutorials/coin_price_list.md)
- [Build a Leaderboard API At Edge using Cloudflare Workers and Redis](https://upstash.com/docs/redis/tutorials/edge_leaderboard.md): This tutorial shows how to build a Leaderboard API At Edge using Cloudflare Workers and Redis.
- [Express Session with Serverless Redis](https://upstash.com/docs/redis/tutorials/express_session.md): This tutorial shows how to use Upstash as the session storage of your Express application.
- [Serverless Golang API with Redis](https://upstash.com/docs/redis/tutorials/goapi.md)
- [Build a Serverless Histogram API with Redis](https://upstash.com/docs/redis/tutorials/histogram.md): This tutorial shows how to build a histogram API with Redis.
- [Job Processing and Event Queue with Serverless Redis](https://upstash.com/docs/redis/tutorials/job_processing.md): This tutorial shows how to use Upstash Redis for job/task processing.
- [Caching in Laravel with Redis](https://upstash.com/docs/redis/tutorials/laravel_caching.md)
- [Next.js with Redis](https://upstash.com/docs/redis/tutorials/nextjs_with_redis.md)
- [Building a Serverless Notification API for Your Web Application with Redis](https://upstash.com/docs/redis/tutorials/notification.md): This tutorial shows how to create a Serverless Notification API for Your Web Application with Redis.
- [Nuxt with Redis](https://upstash.com/docs/redis/tutorials/nuxtjs_with_redis.md): This tutorial shows how to use Upstash inside your Nuxt application.
- [Redis as a Cache for Your FastAPI App](https://upstash.com/docs/redis/tutorials/python_fastapi_caching.md)
- [Multithreaded Web Scraping with Redis Caching](https://upstash.com/docs/redis/tutorials/python_multithreading.md)
- [Rate Limiting for Your FastAPI App](https://upstash.com/docs/redis/tutorials/python_rate_limiting.md)
- [Build a Real-Time Chat Application with Serverless Redis](https://upstash.com/docs/redis/tutorials/python_realtime_chat.md)
- [Manage Sessions in Python with Serverless Redis](https://upstash.com/docs/redis/tutorials/python_session.md)
- [Building a URL Shortener with Redis](https://upstash.com/docs/redis/tutorials/python_url_shortener.md)
- [Serverless Python API with Redis](https://upstash.com/docs/redis/tutorials/pythonapi.md)
- [AWS Lambda Rate Limiting with Serverless Redis](https://upstash.com/docs/redis/tutorials/rate-limiting.md)
- [Redis Queue: From FIFO Lists to a Job Queue](https://upstash.com/docs/redis/tutorials/redis_queue.md): How to build FIFO, reliable, and blocking queues on Upstash Redis, then a delayed and prioritized job queue with a dead-letter queue.
- [Serverless Redisson](https://upstash.com/docs/redis/tutorials/redisson.md): This tutorial shows how to use Upstash with Redisson client.
- [Roadmap Voting App with Serverless Redis](https://upstash.com/docs/redis/tutorials/roadmapvotingapp.md): This is a single page application powered by upstash and next.js.
- [Serverless API with Java and Redis](https://upstash.com/docs/redis/tutorials/serverless_java_redis.md)
- [TanStack AI Chat Persistance](https://upstash.com/docs/redis/tutorials/tanstack_chat_persistence.md): Use Upstash Redis to persist TanStack AI chat histories across reloads, navigation, and devices with a simple adapter.
- [Using AWS SAM](https://upstash.com/docs/redis/tutorials/using_aws_sam.md)
- [Serverless Redis on Google Cloud Run functions](https://upstash.com/docs/redis/tutorials/using_google_cloud_functions.md)
- [Using Serverless Framework](https://upstash.com/docs/redis/tutorials/using_serverless_framework.md)

# Advanced Settings
Source: https://upstash.com/docs/search/features/advanced-settings

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

This page covers the advanced configuration options available in the Upstash Search. These parameters allow you to fine-tune search behavior for your specific use case and requirements.

## Reranking

The `reranking` parameter enables enhanced search result reranking using advanced AI models. It's disabled by default (`false`) and incurs additional costs when enabled.

<CodeGroup>

```typescript TypeScript
const results = await index.search({
  query: "complex technical documentation",
  reranking: true // Enable reranking
});
```

```python Python
results = index.search(
    query="complex technical documentation",
    reranking=True  # Enable reranking
)
```

</CodeGroup>

**Reranking Options:**
* **Standard Reranking** (`reranking: false`, default): Uses a simpler, faster model with no additional cost
* **Advanced Reranking** (`reranking: true`): Uses state-of-the-art models for highest quality results at $1 per 1K operations

Learn more about how reranking works in our [Algorithm documentation](/docs/search/features/algorithm#3-reranking).

## Semantic Weight

The `semanticWeight` parameter controls the balance between semantic search and full-text search in the hybrid search process. It accepts values from 0 to 1, with a default of 0.75 (75% semantic, 25% full-text).

<CodeGroup>

```typescript TypeScript
// More semantic matching (better for conceptual searches)
const semanticResults = await index.search({
  query: "artificial intelligence concepts",
  semanticWeight: 0.9 // 90% semantic, 10% full-text
});

// More keyword matching (better for exact terms)
const keywordResults = await index.search({
  query: "API documentation React hooks",
  semanticWeight: 0.3 // 30% semantic, 70% full-text
});
```

```python Python
# More semantic matching
semantic_results = index.search(
    query="artificial intelligence concepts",
    semantic_weight=0.9  # 90% semantic, 10% full-text
)

# More keyword matching
keyword_results = index.search(
    query="API documentation React hooks",
    semantic_weight=0.3  # 30% semantic, 70% full-text
)
```

</CodeGroup>

**Optimization Guidelines:**
* **Higher semantic weight (0.7-1.0)**: Better for conceptual searches, finding related content, and handling synonyms
* **Lower semantic weight (0.0-0.4)**: Better for exact keyword matching, technical queries, and specific terms

Read more about hybrid search in our [Algorithm documentation](/docs/search/features/algorithm#2-hybrid-vector-search).

## Input Enrichment

The `inputEnrichment` parameter controls whether queries are enhanced using AI before searching. It's enabled by default (`true`) and significantly improves search quality at the cost of some additional latency.

<CodeGroup>

```typescript TypeScript
// Disable input enrichment for faster responses
const results = await index.search({
  query: "space opera",
  inputEnrichment: false // Faster but less enhanced results
});

// Default behavior (enrichment enabled)
const enrichedResults = await index.search({
  query: "space opera"
  // inputEnrichment: true is the default
});
```

```python Python
# Disable input enrichment for faster responses
results = index.search(
    query="space opera",
    input_enrichment=False  # Faster but less enhanced results
)

# Default behavior (enrichment enabled)
enriched_results = index.search(
    query="space opera"
    # input_enrichment=True is the default
)
```

</CodeGroup>

**When to Disable Input Enrichment:**
* When you need the fastest possible response times
* When you want to preserve the exact user query for full-text search

**Benefits of Input Enrichment:**
* Handles typos and alternative phrasings
* Expands queries with related terms and context
* Improves understanding of user intent
* Adds semantic context to ambiguous queries

Learn more about input enrichment in our [Algorithm documentation](/docs/search/features/algorithm#1-input-enrichment).

## Keep Original Query After Enrichment

The `keepOriginalQueryAfterEnrichment` parameter controls whether the original user query is preserved alongside the AI-enriched version during search. It's disabled by default (`false`) and only has an effect when `inputEnrichment` is enabled.

<CodeGroup>

```typescript TypeScript
// Keep both original and enriched queries
const results = await index.search({
  query: "space opera",
  keepOriginalQueryAfterEnrichment: true // Uses both original and enriched
});
```

</CodeGroup>

**When to Enable This Option:**
* When you want to ensure exact keyword matches are included
* When the original query contains specific technical terms or identifiers
* When you want to balance AI enhancement with literal query matching

<Note>
This parameter has no effect when `inputEnrichment` is set to `false`, since there's no enriched query to compare against.
</Note>

## Filter

The `filter` parameter allows you to restrict search results based on content criteria. It accepts either a string expression (SQL-like syntax) or a structured filter object (TypeScript SDK only).

<CodeGroup>

```typescript TypeScript
// String filter expression (SQL-like syntax)
const results = await index.search({
  query: "wireless headphones",
  filter: "category = 'Electronics' AND in_stock > 0"
});

// TypeSafe structured filter (TypeScript SDK only)
const results2 = await index.search({
  query: "wireless headphones",
  filter: {
    AND: [
      { category: { equals: 'Electronics' } },
      { in_stock: { greaterThan: 0 } }
    ]
  }
});
```

```python Python
# String filter expression (SQL-like syntax)
results = index.search(
    query="wireless headphones",
    filter="category = 'Electronics' AND in_stock > 0"
)
```

</CodeGroup>

For detailed information about filter syntax, operators, and examples, see the [Filtering documentation](/docs/search/features/filtering).

## Example: Complete Configuration

Here's an example showing all parameters configured together:

<CodeGroup>

```typescript TypeScript
const results = await index.search({
  query: "machine learning algorithms for data analysis",
  limit: 15,
  filter: "category = 'data-science' AND difficulty_level <= 'intermediate'",
  reranking: true,
  semanticWeight: 0.8,
  inputEnrichment: true
});
```

```python Python
results = index.search(
    query="machine learning algorithms for data analysis",
    limit=15,
    filter="category = 'data-science' AND difficulty_level <= 'intermediate'",
    reranking=True,
    semantic_weight=0.8,
    input_enrichment=True
)
```

</CodeGroup>

This configuration:
* Searches for ML content with enhanced query processing
* Returns up to 15 results
* Filters for data science content at beginner to intermediate levels
* Uses premium reranking for best quality results
* Emphasizes semantic matching (80%) over keyword matching (20%)
* Enables input enrichment for better intent understanding

# Algorithm
Source: https://upstash.com/docs/search/features/algorithm

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

Our algorithm combines AI-powered query enhancement, hybrid search techniques, and intelligent reranking to understand user intent (also known as [search intent](https://backlinko.com/hub/seo/search-intent)) and return the most accurate results.

Upstash Search processes every query through three key stages:
1. **Input Enrichment**: Enhances the search query using AI to better understand user intent.
2. **Hybrid Vector Search**: Combines semantic search and full-text search to find relevant documents.
3. **Reranking**: Uses AI models to reorder results based on relevance.

### 1. Input Enrichment

The first stage enhances your search query using a Large Language Model (LLM). This process:

* Expands the original query with related terms and context
* Improves understanding of user intent
* Handles typos and alternative phrasings
* Adds semantic context that might be missing from the original query

While input enrichment introduces some latency, it significantly improves search quality.

Input enrichment is enabled by default. You can disable this feature if you want to preserve the user query for full text-search or if you want to reduce latency.

<CodeGroup>

```typescript TypeScript
const results = await index.search({
  query: "space opera",
  inputEnrichment: false // faster but less enhanced results
});
```

```python Python
results = index.search(
    query="space opera",
    input_enrichment=False  # faster but less enhanced results
)
```

</CodeGroup>

### 2. Hybrid Vector Search

The second stage performs hybrid search by combining semantic search and full-text search:

* **Semantic Search**: Uses vector embeddings to understand meaning and context
* **Full-Text Search**: Performs traditional keyword matching
* **Result Combination**: Merges results using configurable weights

By default, Upstash Search uses a 75% semantic weight and 25% full-text weight. You can adjust this balance based on your use case:

* Higher semantic weight: Better for conceptual searches and finding related content
* Lower semantic weight: Better for exact keyword matching and technical queries

<CodeGroup>

```typescript TypeScript
const results = await index.search({
  query: "artificial intelligence concepts",
  semanticWeight: 0.9 // 90% semantic, 10% full-text
});
```

```python Python
results = index.search(
    query="artificial intelligence concepts",
    semantic_weight=0.9  # 90% semantic, 10% full-text
)
```

</CodeGroup>

### 3. Reranking

The final stage reranks the hybrid search results using AI models. Upstash Search offers two reranking options:

**Advanced Reranking (`reranking: true`)**
* Uses a powerful, state-of-the-art reranking model
* Provides the highest quality results
* Costs $1 per 1K reranking operations
* Recommended for applications where search quality is critical

**Standard Reranking (`reranking: false`, default)**
* Uses a simpler, faster reranking model
* Still provides significant improvements over raw hybrid results
* No additional cost

<CodeGroup>

```typescript TypeScript
const results = await index.search({
  query: "complex technical documentation",
  reranking: true // uses premium reranking model
});
```

```python Python
results = index.search(
    query="complex technical documentation",
    reranking=True  # uses premium reranking model
)
```

</CodeGroup>

## Conclusion

This three-stage approach ensures that Upstash Search:

* **Understands Intent**: Input enrichment helps the system understand what users are really looking for
* **Finds Relevant Content**: Hybrid search captures both semantic meaning and exact keyword matches
* **Prioritizes Quality**: Reranking ensures the most relevant results appear first
* **Stays Flexible**: Each stage can be configured based on your specific needs

The result is a search system that works well across all kinds of content and domains, handling everything from precise technical queries to broad conceptual searches.

# Content and Metadata
Source: https://upstash.com/docs/search/features/content-and-metadata

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

***

## Content

The `content` field contains the searchable data of your documents. This is what gets indexed and can be queried.

* **Required**: You must provide `content` when upserting documents
* **Format**: JSON object structure
* **Searchable**: All fields within content are indexed for search
* **Filterable**: Content fields can be used in filter queries

<Tabs>

<Tab title="Python">
```py
index.upsert(
  documents=[
    {
      "id": "star-wars",
      "content": { "text": "Star Wars is a sci-fi space opera."}
    }
  ]
)
```
</Tab>

<Tab title="TypeScript">
```ts
await index.upsert([
  {
    id: "star-wars",
    content: { title: "Star Wars", genre: "sci-fi", category: "classic" }
  }
]);
```
</Tab>

</Tabs>

***

## Metadata

The `metadata` field stores additional context about your documents that won't be indexed for search. This is useful for data you want to retrieve with your search results but don't need to search through.

* **Optional**: You can upsert documents without metadata
* **Format**: JSON object structure
* **Not Searchable**: Metadata fields are not indexed

<Tabs>

<Tab title="Python">
```py
index.upsert(
  documents=[
    {
      "id": "star-wars",
      "content": { "text": "Star Wars is a sci-fi space opera."},
      "metadata": {
        "genre": "sci-fi",
      }
    }
  ]
)
```
</Tab>

<Tab title="TypeScript">
```ts
await index.upsert([
  {
    id: "star-wars",
    content: { title: "Star Wars", genre: "sci-fi", category: "classic" },
    metadata: { director: "George Lucas" } ,
  }
]);
```
</Tab>

</Tabs>

***

## Best Practices
| Use Content When | Use Metadata When |
|------------------|-------------------|
| Users need to search for this information | Information is for display/reference only (e.g. IDs) |
| The field is important for finding relevant documents | The field provides context after finding documents |
| You want to filter results by this field | You need to track internal system information |

***

## Examples & Common Patterns

1. E-commerce Products
```javascript
{
  // 👇 searchable and filterable
  content: {
    name: "Wireless Headphones",
    description: "Noise-cancelling bluetooth headphones",
    brand: "Sony",
    category: "Electronics"
  },
  // 👇 not searchable, for reference only
  metadata: {
    sku: "AT-WH-001",
    warehouse_location: "A3-15",
    supplier_id: "SUP-123"
  }
}
```

2. Knowledge Base Articles
```javascript
{
  // 👇 searchable and filterable
  content: {
    title: "How to Reset Your Password",
    body: "Follow these steps to reset your password...",
    tags: ["password", "security", "account"]
  },
  // 👇 not searchable, for reference only
  metadata: {
    author_id: "usr_123",
    version: 3,
    approved_by: "usr_456",
    view_count: 1523
  }
}
```

3. News Articles
```javascript
{
  // 👇 searchable and filterable
  content: {
    headline: "Tech Company Announces New Product",
    excerpt: "In a press conference today...",
    category: "Technology",
    keywords: ["innovation", "product launch"]
  },
  // 👇 not searchable, for reference only
  metadata: {
    source_url: "https://news.example.com/article/123",
    syndication_rights: true,
    word_count: 200
  }
}
```

# Filtering
Source: https://upstash.com/docs/search/features/filtering

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

Search queries with filters only return documents which have the content or metadata matching with the filter.

Upstash Search allows you to filter by content and metadata keys which have the following value types:

* string
* number
* boolean
* object
* array

Filtering is implemented as a combination of in and post-filtering. Every query is assigned a filtering budget,
determining the number of candidate documents that can be compared against the filter during query execution. If this
budget is exceeded, the system fallbacks into post-filtering. Therefore, with highly selective filters, fewer
than `topK` documents may be returned.

***

## Filter Syntax

A filter has a syntax that resembles SQL, which consists of operators on content and metadata keys and boolean operators
to combine them.

To distinguish fields in content and metadata, metadata keys must be prefixed with
the `@metadata` identifier.

Assuming you have content like below:

```typescript
{
  // 👇 searchable and filterable
  content: {
    name: "Wireless Headphones",
    description: "Noise-cancelling bluetooth headphones",
    brand: "Sony",
    category: "Electronics",
    warehouse_location: "A3-15",
    in_stock: 3
  },
  // 👇 not searchable, but filterable
  metadata: {
    sku: "AT-WH-001",
    supplier_id: "SUP-123",
  }
}
```

Filter documents like so:

<Tabs>
<Tab title="Python">
```py
scores = index.search(
    query="sony headphones",
    filter="warehouse_location = 'A3-15' AND @metadata.supplier_id = 'SUP-123'",
)
```
</Tab>
<Tab title="TypeScript">
```ts
const searchResults = await index.search({
    query: "sony headphones",
    filter: "warehouse_location = 'A3-15' AND @metadata.supplier_id = 'SUP-123'",
});
```
</Tab>
</Tabs>

### TypeSafe Filters (TypeScript)

In our [TypeScript SDK](https://github.com/upstash/search-js), we support a typesafe way to build filters:

```ts
import { Search } from "@upstash/search";

type Content = { text: string }
type Metadata = { count: number }

const client = Search.fromEnv()
const searchIndex = client.index<Content, Metadata>("hello world!");

const results = await searchIndex.search({
  query: "hello world!",
  limit: 2,
  filter: {
    AND: [
      // filtering by content field
      { text: { glob: "*test-data*" } },
      // filtering by metadata field (add @metadata prefix)
      { "@metadata.count": { greaterThanOrEquals: 3 } }
    ],
  },
});
```

<Note>
  You can pass type parameters to the `index` method to enable type-safe filters. The first type parameter is for [content fields](/docs/search/features/content-and-metadata#content), and the optional second type parameter is for [metadata fields](/docs/search/features/content-and-metadata).

  Use the `@metadata` prefix to filter by metadata fields in your filter objects. If you don't need to filter by metadata, you can omit the metadata type parameter.
</Note>

You can use the `AND` and `OR` operators to build complex filters.

<CodeGroup>

```ts AND
const searchResults = await index.search({
  query: "sony headphones",
  filter: {
    AND: [
      { warehouse_location: { equals: 'A3-15' } },
      { in_stock: { greaterThan: 0 } }
    ]
  },
});
```

```ts OR
const searchResults = await index.search({
  query: "sony headphones",
  filter: {
    OR: [
      { warehouse_location: { equals: 'A3-15' } },
      { in_stock: { greaterThan: 0 } }
    ]
  },
});
```

```ts Nested AND/OR
const searchResults = await index.search({
  query: "sony headphones",
  filter: {
    AND: [
      { category: { contains: 'electronics' } },
      { OR: [
        { warehouse_location: { equals: 'A3-15' } },
        { in_stock: { greaterThan: 0 } }
      ]}
    ]
  },
});
```

</CodeGroup>

All the operations below except for filtering array elements and nested objects are supported in the typesafe filters.

***

## Operators

#### Equals (=)

The `equals` operator filters content whose values are equal to the given literal.

It is applicable to _string_, _number_, and _boolean_ values.

```SQL
warehouse_location = 'A3-15' AND in_stock = 3
```

***

#### Not Equals (!=)

The `not equals` operator filters content whose values are not equal to the given literal.

It is applicable to _string_, _number_, and _boolean_ values.

```SQL
warehouse_location != 'A3-15' AND in_stock != 3
```

***

#### Less Than (\<)

The `less than` operator filters content whose values are less than the given literal.

It is applicable to _number_ values.

```SQL
in_stock < 3
```

***

#### Less Than or Equals (\<=)

The `less than or equals` operator filters content whose values are less than or equal to the given literal.

It is applicable to _number_ values.

```SQL
in_stock <= 3
```

***

#### Greater Than (>)

The `greater than` operator filters content whose values are greater than the given literal.

It is applicable to _number_ values.

```SQL
in_stock > 3
```

***

#### Greater Than or Equals (>=)

The `greater than or equals` operator filters content whose values are greater than or equal to the given literal.

It is applicable to _number_ values.

```SQL
in_stock >= 3
```

***

#### Glob

The `glob` operator filters content whose values match with the given UNIX glob pattern.

It is applicable to _string_ values.

It is a case sensitive operator.

The glob operator supports the following wildcards:

* `*` matches zero or more characters.
* `?` matches exactly one character.
* `[]` matches one character from the list
  * `[abc]` matches either `a`, `b`, or `c`.
  * `[a-z]` matches one of the range of characters from `a` to `z`.
  * `[^abc]` matches any one character other than `a`, `b`, or `c`.
  * `[^a-z]` matches any one character other than `a` to `z`.

For example, the filter below would only match with warehouse locations whose first character is `A` or `B`.

```SQL
warehouse_location GLOB '[AB]*'
```

***

#### Not Glob

The `not glob` operator filters content whose values do not match with the given UNIX glob pattern.

It is applicable to _string_ values.

It has the same properties with the glob operator.

For example, the filter below would only match with warehouse locations whose first character is anything other than `A`.

```SQL
warehouse_location NOT GLOB 'A*'
```

***

#### In

The `in` operator filters content whose values are equal to any of the given literals.

It is applicable to _string_, _number_, and _boolean_ values.

```SQL
country IN ('Germany', 'Turkey', 'France')
```

Semantically, it is equivalent to equals operator applied to all of the given literals with `OR` boolean operator in between:

```SQL
country = 'Germany' OR country = 'Turkey' OR country = 'France'
```

***

#### Not In

The `not in` operator filters content whose values are not equal to any of the given literals.

It is applicable to _string_, _number_, and _boolean_ values.

```SQL
economy.currency NOT IN ('USD', 'EUR')
```

Semantically, it is equivalent to not equals operator applied to all of the given literals with `AND` boolean operator in between:

```SQL
economy.currency != 'USD' AND economy.currency != 'EUR'
```

***

#### Contains

The `contains` operator filters content whose values contain the given literal.

It is applicable to _array_ values.

```SQL
economy.major_industries CONTAINS 'Tourism'
```

***

#### Not Contains

The `not contains` operator filters content whose values do not contain the given literal.

It is applicable to _array_ values.

```SQL
economy.major_industries NOT CONTAINS 'Steel Production'
```

***

#### Has Field

The `has field` operator filters content which have the given JSON field.

```SQL
HAS FIELD geography.coordinates
```

***

#### Has Not Field

The `has not field` operator filters content which do not have the given JSON field.

```SQL
HAS NOT FIELD geography.coordinates.longitude
```

***

### Boolean Operators

Operators above can be combined with `AND` and `OR` boolean operators to form
compound filters.

```SQL
country = 'Turkey' AND population > 10000000
```

Boolean operators can be grouped with parentheses to have higher precedence.

```SQL
country = 'Turkey' AND (population > 10000000 OR is_capital = false)
```

When no parentheses are provided in ambiguous filters, `AND` will have higher
precedence than `OR`. So, the filter

```SQL
country = 'Turkey' AND population > 10000000 OR is_capital = false
```

would be equivalent to

```SQL
(country = 'Turkey' AND population > 10000000) OR is_capital = false
```

***

### Filtering Nested Objects

It is possible to filter nested object fields by referencing them with the `.` accessor.

Nested fields can be at arbitrary depths, so more than one `.` accessor can be used
in the same identifier.

```SQL
economy.currency != 'USD' AND geography.coordinates.latitude >= 35.0
```

***

### Filtering Array Elements

Apart from the `CONTAINS` and `NOT CONTAINS` operators, individual array elements can also
be filtered by referencing them with the `[]` accessor by their indexes.

Indexing is zero based.

```SQL
economy.major_industries[0] = 'Tourism'
```

Also, it is possible to index from the back using the `#` character with negative values.
`#` can be thought as the number of elements in the array, so `[#-1]` would reference the
last element.

```SQL
economy.major_industries[#-1] = 'Finance'
```

***

### Miscellaneous

* Identifiers (the left side of the operators) should be of the form `[a-zA-Z_][a-zA-Z_0-9.[\]#-]*`. In simpler terms, they should
  start with characters from the English alphabet or `_`, and can continue with same characters plus numbers and other accessors
  like `.`, `[0]`, or `[#-1]`.
* The string literals (strings in the right side of the operators) can be either single or double quoted.
* Boolean literals are represented as `1` or `0`.
* The operators, boolean operators, and boolean literals are case insensitive.

# Indexes
Source: https://upstash.com/docs/search/features/indexes

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

Upstash Search allows you to partition a single database into multiple isolated indexes.
Each index acts as a self-contained subset of the database,
search and upsert requests are limited to one index.

***

## Using an Index

Indexes are created implicitly when an upsert operation is performed,
so there is no specific endpoint for creating an index.

For example, the code snippet below will create the index `foo` if it does not already exist,
upsert and search the document only on that index.

<Tabs>

<Tab title="Python">
```python
index = client.index("foo")
index.upsert( ... )
```
</Tab>

<Tab title="TypeScript">
```ts
const index = client.index("movies");
await index.upsert( ... );
```
</Tab>

</Tabs>

***

## Listing Indexes

Names of all the active indexes of a database can be listed as follows:

<Tabs>

<Tab title="Python">
```python
client.list_indexes()
```
</Tab>

<Tab title="TypeScript">
```ts
await client.listIndexes()
```
</Tab>

</Tabs>

***

## Deleting an Index

Leftover indexes can be deleted as follows:

<Tabs>

<Tab title="Python">
```python
client.delete_index("foo")
```
</Tab>

<Tab title="TypeScript">
```ts
await client.index("foo").deleteIndex();
```
</Tab>

</Tabs>

# Reranking
Source: https://upstash.com/docs/search/features/reranking

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

Upstash Search combines semantic and full text search results for maximum relevancy. Optionally, you can re-rank the returned documents using a state of the art model to further improve relevancy.

We provide this additional re-ranking as an opt-in setting because it requires more computational resources and is charged at $1 per 1K re-ranked documents.

<Tabs>

<Tab title="Python">
```python
scores = index.search(
    query="space opera",
    limit=2,
    reranking=True,
)
```
</Tab>

<Tab title="TypeScript">
```ts
const searchResults = await index.search({
  query: "space opera",
  limit: 2,
  reranking: true,
});
```
</Tab>

</Tabs>

# FAQ
Source: https://upstash.com/docs/search/help/faq

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

***

## Upload & Storage

**How can I upload a large dataset quickly?**

To upload large datasets efficiently, write a script that upserts documents in batches of 100.

**What is the maximum size or number of documents I can index?**

You can index an unlimited number of documents, though the entire database is capped at 50GB. Each document can contain up to 1,500 characters.

**How is storage calculated and charged?**

We charge based on the number of documents you store, not the storage size they take up.

***

## Search & Indexing

**Do you support full-text search?**

Yes, we support full-text search through special embedding models called sparse indexes that simulate full-text search capabilities within the vector space.

**What is the maximum number of search indexes I can create?**

You can create up to 10,000 indexes per database.

**How do I remove specific fields from an indexed document?**

You can simply upsert the same document without the unwanted fields. When you remove or rewrite records, their associated indexes are automatically removed if they are empty.

***

## Upstash Search vs. Upstash Redis Search

**What's the difference between Upstash Search and Upstash Redis Search?**

[Upstash Search](/docs/search/overall/whatisupstashsearch) is a standalone search product that combines semantic and full-text search. [Upstash Redis Search](/docs/redis/search/introduction) is a full-text search extension built into Upstash Redis, based on [Tantivy](https://github.com/quickwit-oss/tantivy).

**When is Upstash Search the better choice?**

Upstash Search is simpler and better for semantic search, where a query returns documents similar to what you searched for along with the exact matches. There is less to manage, since you don't create or maintain an index yourself, and it works without much configuration.

**When is Upstash Redis Search the better choice?**

Upstash Redis Search is a good fit if your use cases can be covered by a fixed set of full-text search operators and you only need the documents that match those operators. It is generally faster and has more powerful commands, such as [counting](/docs/redis/search/counting) and [aggregations](/docs/redis/search/aggregations), and more [filters](/docs/redis/search/querying). It is also convenient if your data is already in Redis, since write operations are tracked automatically once you create an index, and it is globally available because it runs on Upstash Redis.

***

## Billing

**If I upload documents but don't perform searches, will I still be charged?**

Yes, you'll be charged for the number of documents you store, regardless of whether you perform searches on them.

**How are searches and document updates counted for billing?**

Both searches and document updates are counted as requests and charged at the same rate as document creation.

**If a single query returns multiple documents, how is that counted for billing?**

A query that returns multiple documents counts as a single request. If you enable reranking, you'll be charged based on the number of documents reranked, with pricing per 100-document units. For example, reranking 99 documents costs 1 unit, while reranking 101 documents costs 2 units.

# Docusaurus Integration
Source: https://upstash.com/docs/search/integrations/docusaurus

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

## Features

* 🤖 AI-powered search results based on your documentation
* 🎨 Modern and responsive UI
* 🌜 Dark/Light mode support

## Installation

To install the package, run:

```bash
npm install @upstash/docusaurus-theme-upstash-search
```

## Configuration

### Enabling the Searchbar

To enable the searchbar, add the following to your docusaurus config file:

```js
export default {
  themes: ['@upstash/docusaurus-theme-upstash-search'],
  // ...
  themeConfig: {
    // ...
    upstash: {
      upstashSearchRestUrl: "UPSTASH_SEARCH_REST_URL",
      upstashSearchReadOnlyRestToken: "UPSTASH_SEARCH_READ_ONLY_REST_TOKEN",
      upstashSearchIndexName: "UPSTASH_SEARCH_INDEX_NAME",
    },
  },
};
```

The default index name is `docusaurus`. You can override it by setting the `upstashSearchIndexName` option.

You can fetch your URL and read only token from [Upstash Console](https://console.upstash.com/search). **Make sure to use the read only token!**

If you do not have a search database yet, you can create one from [Upstash Console](https://console.upstash.com/search). Make sure to use Upstash generated embedding model.

## Indexing Your Documentation

### Setting Up Environment Variables

To index your documentation, create a `.env` file with the following environment variables:

```bash
UPSTASH_SEARCH_REST_URL=
UPSTASH_SEARCH_REST_TOKEN=
UPSTASH_SEARCH_INDEX_NAME=
DOCS_PATH=
```

You can fetch your URL and token from [Upstash Console](https://console.upstash.com/search). This time **do not use the read only token** since we are upserting data.

### Running the Indexing Script

After setting up your environment variables, run the indexing command:

```bash
npx index-docs-upstash
```

### Configuration Options

* **DOCS_PATH**: The indexing script looks for documentation in the `docs` directory by default. You can specify a different path using the `DOCS_PATH` option.
* **UPSTASH_SEARCH_INDEX_NAME**: The default index name is `docusaurus`. You can override it by setting the `UPSTASH_SEARCH_INDEX_NAME` option. Make sure the name you set while indexing matches with your themeConfig `upstashSearchIndexName` option.

For more details on how this integration works, check out [the official repository](https://github.com/upstash/docusaurus-theme-upstash-search).

# Getting Started
Source: https://upstash.com/docs/search/overall/getstarted

import { Catalog } from "/snippets/catalog.jsx";

***

<iframe
  id="intro-video" width='560' height='315'
  src='https://www.youtube.com/embed/xVwdHZEkdGI?rel=0&disablekb=1'
  title='YouTube video player' frameBorder='0' allow='accelerometer; fullscreen;
  clipboard-write; encrypted-media; gyroscope' allowFullScreen>
</iframe>

***

<Tip href="/redis/search/introduction" >

We now have **[Redis Search](/docs/redis/search/introduction)** in Upstash.

If you are starting a new project, we recommend using **Redis Search**:

* Index and query your Redis data with a schema.
* Use advanced query operators and aggregations.
* Keep your data and search in one place.

See [Redis Search Introduction](/docs/redis/search/introduction) for details.
</Tip>

***

## Quickstart

Check out our Next.js quickstart guide if you're working in Next.js.

<CardGroup cols={1}>
  <Card title="Next.js" icon="node-js" href="/search/tutorials/nextjs">
    Use Upstash Search in your Next.js app
  </Card>
</CardGroup>

***

## Create a Database

Create a Search Database by navigating to the `Vector` tab and clicking on the `Search Database` button under `Create`.

  <img />

A dialog with the following options will open:

* **Name:** Type a name for your database (e.g. "product-search").

* **Region:** Choose the region for your database. For best performance, select the region closest to your application.

  _We plan to support additional regions and cloud providers. Feel free to send your requests to [support@upstash.com](mailto:support@upstash.com)._

Once you're done, click `Next`, choose a plan, and your Database is ready:

  <img />

***

## Add Documents

Add documents to your database using our REST API, our SDKs, or directly in the dashboard.

### 1. Add Documents via Dashboard

Navigate to the `Data Browser` section of your Database and click `Upsert Documents`:

  <img />

A dialog with the following options will open:

  <img />

* **Index:** An [index](/docs/search/features/indexes) to group your data.

  _If you plan to query all documents in one place, you only need one index (e.g. "product-search"). If you plan to add multi-tenancy, so that each user can only search their own data, for example, you can create one index per user ("user-1", "user-2", etc.)._

* **ID:** An automatically generated ID.

* **Content:** The searchable data in JSON format.

* **Metadata:** Optional information attached to this document.

More information about content and metadata can be found [here](/docs/search/features/content-and-metadata).

***

### 2. Add Documents via SDKs

<Tabs>

<Tab title="Python">

```python
from upstash_search import Search

client = Search(
    url="<UPSTASH_SEARCH_REST_URL>",
    token="<UPSTASH_SEARCH_REST_TOKEN>",
)

index = client.index("movies")

index.upsert(
    documents=[
        {
            "id": "movie-0",
            "content": {
                "title": "Star Wars",
                "overview": "Sci-fi space opera",
                "genre": "sci-fi",
                "category": "classic",
            },
            "metadata": {
                "poster": "https://poster.link/starwars.jpg",
            },
        },
    ],
)
```

</Tab>

<Tab title="TypeScript">

```ts
import { Search } from "@upstash/search"

const client = new Search({
  url: "<SEARCH_INDEX_REST_URL>",
  token: "<SEARCH_INDEX_REST_TOKEN>",
})

const index = client.index("movies")

await index.upsert([
  {
    id: "star-wars",
    content: { title: "Star Wars", genre: "sci-fi", category: "classic" },
    metadata: { director: "George Lucas" },
  },
])
```

</Tab>

</Tabs>

***

## Search Your Database

You can search across your Database the same way: using our REST API, our SDKs or directly in your dashboard.

### 1. Searching via Dashboard

To search your documents, enter a search term and click `Search`:

  <img />

### 2. Searching Data via SDKs

<Tabs>

<Tab title="Python">
```py
scores = index.search( query="space opera", limit=2, )
```
</Tab>

<Tab title="TypeScript">
```ts
const searchResults = await index.search({
  query: "space opera",
  limit: 2,
  reranking: true,
});
```
</Tab>

</Tabs>

***

**That's it!** 🎉 You've just created your first serverless search database with Upstash Search!

But this is just the beginning. Upstash Search also supports:

* Advanced reranking
* Fine-grained control over search results
* Metadata-based filtering

We'll get into those features in the next sections of this documentation. For now, you've already mastered the basics!

## SDKs

<Catalog product="search" type="sdk" cols={3} />

# Pricing & Limits
Source: https://upstash.com/docs/search/overall/pricing

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

Please check our [pricing page](https://upstash.com/pricing/search) for the most up-to-date information on pricing and limits.

# What is Upstash Search?
Source: https://upstash.com/docs/search/overall/whatisupstashsearch

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

Upstash Search is a **simple, lightweight, and scalable way to add AI-powered search to your app**.

We combine full-text and semantic search for highly relevant results. Search works out of the box and scales to massive data sizes with zero infrastructure to manage.

<Note>
Looking for full-text search inside an Upstash Redis database instead? See [Upstash Redis Search](/docs/redis/search/introduction), a full-text search extension built into Upstash Redis. Upstash Search (this page) is a standalone product that combines full-text and semantic search.
</Note>

***

## Lightweight & Efficient

Most search products (we'll avoid names here 💀) are bloated, complicated, and hard to manage. We're building Upstash search to be the exact opposite: fast to set up, easy to use and optimized for real-world use cases.

* Set up in minutes
* Plug-and-play AI search with smart defaults
* Optimized for speed and simplicity

***

## Fast, Relevant Results

We have a deep understanding of LLM technology through [Upstash Vector](/docs/vector/overall/whatisvector) and hosting our own models at scale. We're now using that experience to make search feel truly intelligent. While most search products add AI to catch up, we're making it a core part of Upstash Search from the start.

* Combines semantic & full-text search for relevancy
* Understands user search intent
* Smart ranking shows the best matches first

***

## Scales Automatically

We've scaled [Upstash Redis](/docs/redis/overall/getstarted) to serve billions of requests each day with extremely high availability. That same experience is built into Search, so you never have to think about infra.

We're building Search for modern, serverless stacks from the ground up. It's ready for any data size you throw at it.

* Perfect for serverless apps and modern frameworks like Next.js
* Scales to any data size (seriously, we've indexed the entire Wikipedia in 7 languages)
* No infrastructure, clusters or servers to manage

***

## Start using Upstash Search

Whether you're building a side project or scaling your company, Upstash Search gives you fast, smart, production-ready search with zero infra to manage. Try it today, it only takes a few minutes to [get started](/docs/search/overall/getstarted)!

- [Delete](https://upstash.com/docs/search/sdks/py/commands/delete.md)
- [Fetch](https://upstash.com/docs/search/sdks/py/commands/fetch.md)
- [Info](https://upstash.com/docs/search/sdks/py/commands/info.md)
- [Range](https://upstash.com/docs/search/sdks/py/commands/range.md)
- [Reset](https://upstash.com/docs/search/sdks/py/commands/reset.md)
- [Search](https://upstash.com/docs/search/sdks/py/commands/search.md)
- [Upsert](https://upstash.com/docs/search/sdks/py/commands/upsert.md)

# Getting Started
Source: https://upstash.com/docs/search/sdks/py/gettingstarted

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

`upstash-search` is a Python SDK for Upstash AI Search, enabling easier operations on Search Databases.

Using `upstash-search` you can:

* Perform AI-powered search queries with or without reranking.
* Upsert documents with metadata to an index.
* Fetch documents by their IDs.
* Delete documents from a database.
* Access database stats.
* Reset everything related to a database.

You can find the GitHub Repository [here](https://github.com/upstash/search-py).

## Install

```bash
pip install upstash-search
```

## Usage

### Initializing the client

There are two pieces of configuration required to use the Upstash search client: a REST token and REST URL. These values can be passed using environment variables or in code through a configuration object. Find your configuration values in [the console dashboard](https://console.upstash.com/search).

#### Using environment variables

The environment variables used to configure the client are the following. You can follow [this guide](/docs/search/overall/getstarted) to retrieve credentials.

```bash
UPSTASH_SEARCH_REST_URL="your_rest_url"
UPSTASH_SEARCH_REST_TOKEN="your_rest_token"
```

When these environment variables are set, the client constructor does not require any additional arguments.

```python
from upstash_search import Search

client = Search.from_env()
```

#### Using a configuration object

If you prefer to pass configuration in code, the constructor accepts a config object containing the `url` and `token` values. This
could be useful if your application needs to interact with multiple databases, each with a different configuration.

```python
from upstash_search import Search

client = Search(
    url="<SEARCH_INDEX_REST_URL>",
    token="<SEARCH_INDEX_REST_TOKEN>",
)
```

***

### Using an Index

The `Search` client is for operations that are about manipulating the Search database. With it, you can create indexes which is where you can upsert and search documents.

```python
index = client.index("films")

index.upsert(
    documents=[
        {
            "id": "movie-0",
            "content": {
                "title": "Star Wars",
                "overview": "Sci-fi space opera",
                "genre": "sci-fi",
                "category": "classic",
            },
            "metadata": {
                "poster": "https://poster.link/starwars.jpg",
            },
        },
    ],
)
```

## Telemetry

This sdk sends anonymous telemetry data to help us improve your experience.
We collect the following:

* SDK version
* Platform (Cloudflare, AWS or Vercel)
* Runtime version (python@vX)

You can opt out using the allow_telemetry flag when creating the client:

```ts
client = Search(
    url="UPSTASH_SEARCH_REST_URL",
    token="UPSTASH_SEARCH_REST_TOKEN",
    allow_telemetry=False,
)
```

- [Delete](https://upstash.com/docs/search/sdks/ts/commands/delete.md)
- [Fetch](https://upstash.com/docs/search/sdks/ts/commands/fetch.md)
- [Info](https://upstash.com/docs/search/sdks/ts/commands/info.md)
- [Range](https://upstash.com/docs/search/sdks/ts/commands/range.md)
- [Reset](https://upstash.com/docs/search/sdks/ts/commands/reset.md)
- [Search](https://upstash.com/docs/search/sdks/ts/commands/search.md)
- [Upsert](https://upstash.com/docs/search/sdks/ts/commands/upsert.md)

# Contributing
Source: https://upstash.com/docs/search/sdks/ts/contributing

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

## Preparing the environment

This project uses [Bun](https://bun.sh/) for packaging and dependency management. Make sure you have the relevant dependencies.

```commandline
curl -fsSL https://bun.sh/install | bash
```

You will also need a search database on [Upstash](https://console.upstash.com/search).

***

## Code Formatting

Run the following command to format code:

```bash
bun run fmt
```

***

## Running tests

To run all the tests, make sure you have the relevant environment variables.

```bash
bun run test
```

# Getting Started
Source: https://upstash.com/docs/search/sdks/ts/getting-started

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

`@upstash/search` is a TypeScript SDK for Upstash AI Search.

Using `@upstash/search` you can:

* Perform AI-powered search queries
* Upsert documents to an index
* Fetch documents by their IDs
* Delete documents from a database
* Access database stats
* Reset databases

You can find the GitHub Repository [here](https://github.com/upstash/search-js).

***

## Installation

<CodeGroup>
```bash npm
npm install @upstash/search
```

```bash yarn
yarn add @upstash/search
```

```bash pnpm
pnpm add @upstash/search
```

```bash bun
bun install @upstash/search
```

</CodeGroup>

***

## Usage

### Initializing the client

There are two pieces of configuration required to use the Upstash search client:
* a REST token
* a REST URL

 These values can be passed using environment variables or through a configuration object. Find these connection details in [the console dashboard](https://console.upstash.com/search).

 ---

#### Using environment variables (recommended)

You can follow [this guide](/docs/search/overall/getstarted) to retrieve the following credentials.

```bash
UPSTASH_SEARCH_REST_URL="your_rest_url"
UPSTASH_SEARCH_REST_TOKEN="your_rest_token"
```

When these environment variables are set, the client constructor does not require any additional arguments.

```typescript
import { Search } from "@upstash/search";

const client = Search.fromEnv();
const index = client.index("movies")
```

***

#### Using a configuration object

The `Search` class accepts a config object containing the `url` and `token` values. This
could be useful if your application needs to interact with multiple databases, each with a different configuration.

```typescript
import { Search } from "@upstash/search";

const client = new Search({
  url: "<SEARCH_INDEX_REST_URL>",
  token: "<SEARCH_INDEX_REST_TOKEN>",
});

const index = client.index("movies")
```

***

#### Typescript

The Search SDK supports defining your content and metadata types at the index level for complete type-safety.

```typescript {4,5,6}
import { Search } from "@upstash/search";
const client = new Search();

type Content = { title: string, genre: string };
type Metadata = { year: number };
const index = client.index<Content, Metadata>("movies");

await index.upsert({
  id: "document-id",
  content: { title: "Star Wars", genre: "sci-fi" },
  metadata: { year: 1977 }
})
```

Passing a `Content` type at the index level will provide type safety for the content coming back from or required for the following commands:
* `search`
* `upsert`
* `fetch`
* `range`

In cases you don't want to define a content type at the index level, you can override the index level type definition for a specific command:

```typescript
const results = await index.upsert<Content>({
  id: "id",
  content: { title: "Movie title", ... }
});
```

## Telemetry

This sdk sends anonymous telemetry data to help us improve your experience.
We collect the following:

* SDK version
* Platform (Cloudflare, AWS or Vercel)
* Runtime version (node@18.x)

You can opt out by setting the `UPSTASH_DISABLE_TELEMETRY` environment variable
to any truthy value.

```sh
UPSTASH_DISABLE_TELEMETRY=1
```

Alternatively, you can disable telemetry programmatically:

```ts
const index = client.index("movies", {
  enableTelemetry: false,
});
```

# Database Migrator
Source: https://upstash.com/docs/search/tools/databasemigrator

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

## Introduction

This tool helps you to migrate your data from other service providers (e.g. Algolia, Meilisearch)
to your Upstash Search Database.

### Migrate Using npx
The command below prompts you to provide credentials for the indexes that you want to do the migration between.

```sh
npx @upstash/search-migrator
```

### Using Flags

You can also provide your credentials and other information as command-line flags.
Here are some examples:

#### Algolia to Upstash

```sh
npx @upstash/search-migrator \
  --upstash-url "UPSTASH_SEARCH_REST_URL" \
  --upstash-token "UPSTASH_SEARCH_REST_TOKEN" \
  --algolia-app-id "YOUR_ALGOLIA_APP_ID" \
  --algolia-api-key "YOUR_ALGOLIA_WRITE_API_KEY"
```

#### Meilisearch to Upstash

```sh
npx @upstash/search-migrator \
  --upstash-url "UPSTASH_SEARCH_REST_URL" \
  --upstash-token "UPSTASH_SEARCH_REST_TOKEN" \
  --meilisearch-host "YOUR_MEILISEARCH_HOST" \
  --meilisearch-api-key "YOUR_MEILISEARCH_API_KEY"
```

## Obtaining Credentials

### Upstash

1. Go to your [Upstash Console](https://console.upstash.com/).
2. Select your Search Database.
3. Under the **Details** section, you will find your `UPSTASH_SEARCH_REST_URL` and `UPSTASH_SEARCH_REST_TOKEN`.
   * `--upstash-url` corresponds to `UPSTASH_SEARCH_REST_URL`.
   * `--upstash-token` corresponds to `UPSTASH_SEARCH_REST_TOKEN`.

* You may want to check out
[@upstash/search-migrator](https://www.npmjs.com/package/@upstash/search-migrator)
to see how to find credentials for other service providers

## Migration Process

The migrator will:

1. **Connect** to your source database (Algolia or Meilisearch)
2. **Fetch** all documents from the specified index
3. **Transform** the data to match Upstash Search's format
4. **Upload** the documents to your Upstash Search database
5. **Verify** the migration by comparing document counts

### Data Transformation

The migrator automatically handles the transformation of your data:

* **Document IDs**: Preserved from the source
* **Content**: Mapped to Upstash Search's content field
* **Metadata**: Preserved as metadata in Upstash Search
* **Searchable fields**: All fields become searchable by default

<Note>
  For free tier, 10000 documents can be upserted daily,
  so a database migration with more than 10000 entries could
  be interrupted.
</Note>

### Getting Help

If you encounter any issues during migration:

1. Check the error messages for specific details
2. Verify your credentials are correct
3. Ensure your source database is accessible
4. Contact support at [support@upstash.com](mailto:support@upstash.com)

## Final Remarks
If you've come to this point without any issues, congratulations! you may resume your work
with upstash search, an advanced, developer frinedly search product.

For further insights, please visit [@upstash/search-migrator](https://www.npmjs.com/package/@upstash/search-migrator)

# Documentation Crawler
Source: https://upstash.com/docs/search/tools/documentationcrawler

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

## Introduction

This tool helps you crawl documentation websites incrementally, extract their content, and create a search index in Upstash Search.

## Usage

It is available both as a CLI tool and a library.

### CLI Usage

You can run the CLI directly using `npx` (no installation required):

```sh
npx @upstash/search-crawler
```

Or with command-line options:

```sh
npx @upstash/search-crawler \
  --upstash-url "UPSTASH_SEARCH_REST_URL" \
  --upstash-token "UPSTASH_SEARCH_REST_TOKEN" \
  --index-name "my-index" \
  --doc-url "https://example.com/docs"
```

You will be prompted for any missing options:

* Your Upstash Search URL
* Your Upstash Search token
* (Optional) Custom index name
* The documentation URL to crawl

#### What the Tool Does

1. **Discover** all internal documentation links
2. **Crawl** each page and extract content
3. **Track** new or obsolete data
4. **Upsert** the new records into your Upstash Search index

### Library Usage

You can also use this as a library in your own code:

```typescript
import {
  crawlAndIndex,
  type CrawlerOptions,
  type CrawlerResult,
} from "@upstash/search-crawler";

const options: CrawlerOptions = {
  upstashUrl: "UPSTASH_SEARCH_REST_URL",
  upstashToken: "UPSTASH_SEARCH_REST_TOKEN",
  indexName: "my-docs",
  docUrl: "https://example.com/docs",
  silent: true, // no console output
};

const result: CrawlerResult = await crawlAndIndex(options);
```

## Obtaining Upstash Credentials

1. Go to your [Upstash Console](https://console.upstash.com/).
2. Select your Search index. (See [How to Create Search Index](/docs/search/overall/getstarted#create-a-database))
3. Under the **Details** section, copy your `UPSTASH_SEARCH_REST_URL` and `UPSTASH_SEARCH_REST_TOKEN`.
   * `--upstash-url` corresponds to `UPSTASH_SEARCH_REST_URL`
   * `--upstash-token` corresponds to `UPSTASH_SEARCH_REST_TOKEN`

## Further Reading

Try combining this tool with [Qstash Schedule](/docs/qstash/features/schedules) to keep your database up to date with docs. You may deploy your crawler on a server and call it on a schedule regularly to fetch updates in your docs. Check out our example project for implementation details: [A modern documentation library to search and track the docs.](https://github.com/upstash/search-js/tree/main/examples/search-docs)

For further insights, see [@upstash/search-crawler](https://github.com/upstash/search-crawler)

# Docs Search Quickstart
Source: https://upstash.com/docs/search/tutorials/buildsearchbar

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

***

## Introduction

Upstash Search makes it easy to add a fast, ready-to-use search bar to
your docs site, no complex setup needed. In this tutorial, you’ll learn
how to quickly integrate a modern search experience that helps your users
find what they need. With just a few tweaks, you can use this solution in
any project and deliver great search lightning fast.

***

### 1. Project Setup

First, create an Upstash Search Database
if you don't already have one ([Getting Started guide](/docs/search/overall/getstarted))
and then create a new Next.js application and install the related packages:

```shell
npx create-next-app@latest search-docs-app
cd search-docs-app
npm install @upstash/search @upstash/search-ui lucide-react
```

***

### 2. Add Environment Variables

Find the environment variables from your database dashboard and add them to your `.env` file:

```bash
NEXT_PUBLIC_UPSTASH_SEARCH_URL=<YOUR_SEARCH_REST_URL>
NEXT_PUBLIC_UPSTASH_SEARCH_READONLY_TOKEN=<YOUR_SEARCH_READONLY_TOKEN>
```

***

### 3. Create the Component

Create the [search component](https://github.com/upstash/search-ui) in `app/components/search-bar.tsx`:

```typescript title="app/components/search-bar.tsx"
"use client"

import { SearchBar } from "@upstash/search-ui"
import "@upstash/search-ui/dist/index.css"
import { Search } from "@upstash/search"
import { FileText } from "lucide-react"

const client = new Search({
  url: process.env.NEXT_PUBLIC_UPSTASH_SEARCH_URL! ,
  token: process.env.NEXT_PUBLIC_UPSTASH_SEARCH_READONLY_TOKEN!,
})
// 👇 your search index name
const index = client.index<{ title: string }>("default")

export default function SearchComponent() {
  return (
    <div className="max-w-sm mt-24 mx-auto">
      <SearchBar.Dialog>
        <SearchBar.DialogTrigger placeholder="Search..." />

        <SearchBar.DialogContent>
          <SearchBar.Input placeholder="Type to search..." />
          <SearchBar.Results
            searchFn={(query) => {
              // 👇 100% type-safe: whatever you return here is
              // automatically typed as `result` below
              return index.search({ query, limit: 10, reranking: true })
            }}
          >
            {(result) => (
                <div key={result.id} onClick={() => {
                   window.open(result.metadata?.url as string, "_blank")
                 }}>
               <SearchBar.Result
                 value={result.id}
                 className="cursor-pointer hover:bg-gray-50 transition-colors duration-200"
               >
                <SearchBar.ResultIcon>
                    <FileText className="text-gray-600" />
                </SearchBar.ResultIcon>

                <SearchBar.ResultContent>
                  <SearchBar.ResultTitle>
                    {result.content.title}
                  </SearchBar.ResultTitle>
                  <p className="text-xs text-gray-500 mt-0.5">Docs</p>
                </SearchBar.ResultContent>
              </SearchBar.Result>
              </div>
            )}
          </SearchBar.Results>
        </SearchBar.DialogContent>
      </SearchBar.Dialog>
    </div>
  )
}
```

***

### 4. Crawl Docs to Feed the Component

Call [`npx @upstash/search-crawler`](https://github.com/upstash/search-crawler)
in your command line and follow the CLI, you will be prompted to provide:

* Upstash Search URL (as set in your environment variables)
* Upstash Search Rest Token (as set in your environment variables)
* Upstash Search Index Name (Go for `default` for convenience)
* Docs URL to crawl (Let's go for `https://upstash.com/docs`)
<Note>
  If you prefer not to choose `default` index name,
  don't forget to update the line in the `SearchComponent`
  where you provide the index name.
</Note>

***

### 5. Prepare the UI

Replace the following code snippet with the code in `app/page.tsx`:

```typescript title="app/page.tsx"
import SearchComponent from "./components/search-bar";

export default function Home() {
  return (
    <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-indigo-50">
      <main className="flex-1">
        <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pt-20 pb-16">
          <div className="text-center mb-12">
            <h2 className="text-4xl md:text-5xl font-bold text-gray-900 mb-6">
              Search Upstash Documentation
            </h2>
            <p className="text-xl text-gray-600 max-w-2xl mx-auto leading-relaxed">
              Find exactly what you're looking for in our comprehensive documentation.
              Search through guides, APIs, tutorials, and more with lightning-fast results.
            </p>
          </div>
          {/* Search Component */}
          <div className="max-w-2xl mx-auto">
            <SearchComponent />
          </div>
          <div className="mt-20 grid md:grid-cols-3 gap-8">
            <div className="text-center">
              <div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mx-auto mb-4">
                <svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
                    d="M13 10V3L4 14h7v7l9-11h-7z" />
                </svg>
              </div>
              <h3 className="text-lg font-semibold text-gray-900 mb-2">Lightning Fast</h3>
              <p className="text-gray-600">Get instant search results powered by advanced indexing</p>
            </div>
            <div className="text-center">
              <div className="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center mx-auto mb-4">
                <svg className="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
                    d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
                </svg>
              </div>
              <h3 className="text-lg font-semibold text-gray-900 mb-2">Accurate Results</h3>
              <p className="text-gray-600">Reranking ensures the most relevant content appears first</p>
            </div>
            <div className="text-center">
              <div className="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center mx-auto mb-4">
                <svg className="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
                    d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168
                  18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5
                  5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746
                  0-3.332.477-4.5 1.253" />
                </svg>
              </div>
              <h3 className="text-lg font-semibold text-gray-900 mb-2">Comprehensive</h3>
              <p className="text-gray-600">Search across all documentation, guides, and API references</p>
            </div>
          </div>
        </div>
      </main>
    </div>
  );
}

```

***

### 6. Start the Project

Run the following command to start the development server:

```bash
npm run dev
```

Open your browser and navigate to `http://localhost:3000` to test the application.

You can search through Upstash docs, and results will redirect you to the page you are looking for.

***

### Next Steps

Learn more about:

* [Typescript SDK](/docs/search/sdks/ts/getting-started)
* [Docusaurus Integration](/docs/search/integrations/docusaurus)

# Next.js Search Quickstart
Source: https://upstash.com/docs/search/tutorials/nextjs

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

***

### 1. Create a Search Database

Follow the instructions in the [Getting Started guide](/docs/search/overall/getstarted) to create a Search Database.

***

### 2. Project Setup

Let's create a new Next.js application and install the `@upstash/search` package:

```shell
npx create-next-app@latest search-app
cd search-app
npm install @upstash/search
```

***

### 3. Add Environment Variables

Find the environment variables from your database dashboard and add them to your `.env` file:

```bash
UPSTASH_SEARCH_REST_URL=<YOUR_SEARCH_REST_URL>
UPSTASH_SEARCH_REST_TOKEN=<YOUR_SEARCH_REST_TOKEN>
```

***

### 4. Create an API Route to Upsert Documents

Create an API route in `app/api/upsert/route.ts`:

```typescript title="app/api/upsert/route.ts"
import { Search } from "@upstash/search"

const client = new Search({
  url: process.env.UPSTASH_SEARCH_REST_URL,
  token: process.env.UPSTASH_SEARCH_REST_TOKEN,
})

const index = client.index("my-index")

export async function POST() {
  await index.upsert([
    {
      id: "movie-1",
      content: {
        title: "Inception",
        description: "A thriller about dreams within dreams.",
      },
      metadata: { genre: "sci-fi", year: 2010 },
    },
    {
      id: "movie-2",
      content: {
        title: "The Godfather",
        description: "A story about a powerful Italian-American crime family.",
      },
      metadata: { genre: "crime", year: 1972 },
    },
    {
      id: "movie-3",
      content: {
        title: "The Dark Knight",
        description: "A tale of Batman's fight against the Joker.",
      },
      metadata: { genre: "action", year: 2008 },
    },
  ])

  return new Response("OK")
}
```

***

### 5. Create a Route to Search Documents

Create an API route in `app/api/search/route.ts`:

```typescript title="app/api/search/route.ts"
import { Search } from "@upstash/search"

const client = new Search({
  url: process.env.UPSTASH_SEARCH_REST_URL,
  token: process.env.UPSTASH_SEARCH_REST_TOKEN,
})

const index = client.index("my-index")

export async function POST(req: Request) {
  const { query } = (await req.json()) as { query: string }

  const results = await index.search({ query })

  return new Response(JSON.stringify(results))
}
```

***

### 6. Create a Simple Page

Add the following code in `app/page.tsx`:

```typescript title="app/page.tsx"
"use client";

import { useState } from "react";

interface SearchResult {
  id: string;
  content: Record<string, unknown>;
  metadata: Record<string, unknown>;
  score: number;
}

export default function Home() {
  const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
  const [query, setQuery] = useState("");
  const [isUpserting, setIsUpserting] = useState(false);
  const [upsertSuccess, setUpsertSuccess] = useState(false);

  const upsertData = async () => {
    setIsUpserting(true);
    setUpsertSuccess(false);
    await fetch("/api/upsert", { method: "POST" });
    setIsUpserting(false);
    setUpsertSuccess(true);
  };

  const search = async () => {
    const res = await fetch(`/api/search`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ query }),
    });
    const data = await res.json();
    setSearchResults(data || []);
  };

  return (
    <main className="min-h-screen bg-emerald-50 flex flex-col items-center py-10">
      <div className="w-full max-w-2xl bg-white shadow-md rounded-lg p-6">
        <header className="mb-6 text-center">
          <h1 className="text-2xl font-bold text-emerald-800">Search App</h1>
          <p className="text-emerald-600">
            Upsert data and search through the database.
          </p>
        </header>

        <div className="mb-6">
          <button
            onClick={upsertData}
            disabled={isUpserting}
            className={`w-full py-2 px-4 rounded-md text-white ${
              isUpserting
                ? "bg-emerald-300"
                : "bg-emerald-500 hover:bg-emerald-600"
            }`}
          >
            {isUpserting ? "Upserting..." : "Upsert Data"}
          </button>
          {upsertSuccess && (
            <p className="mt-2 text-sm text-emerald-600">
              Data upserted successfully!
            </p>
          )}
        </div>

        <div className="mb-6">
          <div className="flex gap-2">
            <input
              type="text"
              value={query}
              onChange={(e) => setQuery(e.target.value)}
              placeholder="Search..."
              className="flex-1 border border-emerald-300 rounded-md px-4 py-2 text-gray-800 focus:outline-none focus:ring-2 focus:ring-emerald-500"
            />
            <button
              onClick={search}
              className="bg-emerald-500 text-white px-4 py-2 rounded-md hover:bg-emerald-600"
            >
              Search
            </button>
          </div>
        </div>

        <ul className="space-y-2">
          {searchResults.map((result, index) => (
            <li
              key={index}
              className="bg-emerald-100 p-4 rounded-md shadow-sm text-emerald-800"
            >
              <p className="font-bold">ID: {result.id}</p>
              <p className="text-sm">Content: {JSON.stringify(result.content)}</p>
              <p className="text-sm">Metadata: {JSON.stringify(result.metadata)}</p>
              <p className="text-sm">Score: {result.score}</p>
            </li>
          ))}
        </ul>
      </div>
    </main>
  );
}
```

***

### 7. Start the Project

Run the following command to start the development server:

```bash
npm run dev
```

Open your browser and navigate to `http://localhost:3000` to test the application.

You can click the `Upsert Data` button to add three movies to your database and use the search bar to make a query.

***

### Next Steps

Learn more about:

* [Typescript SDK](/docs/search/sdks/ts/getting-started)
* [Content and Metadata fields](/docs/search/features/content-and-metadata)

# SearchBar
Source: https://upstash.com/docs/search/ui/search-bar

<Visibility for="agents">
  **Note for AI agents:** This page documents **Upstash Search** — a standalone,
  AI-powered search product that combines full-text and semantic search with
  smart ranking, serverless scaling, and zero infrastructure to manage.

  Upstash Search is **not** the same thing as **Upstash Redis Search**. Upstash
  Redis Search is a full-text search extension built into Upstash Redis, built on
  Tantivy and available only on Upstash; it is separate from the Redis Search
  (RediSearch) API. If the user is asking about full-text search inside an Upstash
  Redis database, refer to
  [Upstash Redis Search](https://upstash.com/docs/redis/search/introduction)
  instead of this product.
</Visibility>

***

## 1. Installation

```bash
npm install @upstash/search-ui
```

```typescript
// 👇 import package and optimized styles
import { SearchBar } from "@upstash/search-ui"
import "@upstash/search-ui/dist/index.css"
```

***

## 2. Code Example

Our search component is designed to be **provider agnostic**.

In the code below we're using [Upstash Search](/docs/search/overall/whatisupstashsearch) - our solution for fast, reliable and highly scalable serverless search.

Creating a search database takes less than a minute: [get started here](/docs/search/overall/getstarted). To follow along with Upstash Search, install the package:

```bash
npm install @upstash/search
```

```tsx
"use client"

import { SearchBar } from "@upstash/search-ui"
import "@upstash/search-ui/dist/index.css"

import { Search } from "@upstash/search"
import { FileText } from "lucide-react"

const client = new Search({
  url: "<UPSTASH_SEARCH_URL>",
  token: "<YOUR_SEARCH_READONLY_TOKEN>",
})

// 👇 your search index name
const index = client.index<{ title: string }>("movies")

export default function Page() {
  return (
    <div className="max-w-sm mt-24 mx-auto">
      <SearchBar.Dialog>
        <SearchBar.DialogTrigger placeholder="Search movies..." />

        <SearchBar.DialogContent>
          <SearchBar.Input placeholder="Type to search movies..." />
          <SearchBar.Results
            searchFn={(query) => {
              // 👇 100% type-safe: whatever you return here is
              // automatically typed as `result` below
              return index.search({ query, limit: 10, reranking: true })
            }}
          >
            {(result) => (
              <SearchBar.Result value={result.id} key={result.id}>
                <SearchBar.ResultIcon>
                  <FileText className="text-gray-600" />
                </SearchBar.ResultIcon>

                <SearchBar.ResultContent>
                  <SearchBar.ResultTitle>
                    {result.content.title}
                  </SearchBar.ResultTitle>
                  <p className="text-xs text-gray-500 mt-0.5">Movie</p>
                </SearchBar.ResultContent>
              </SearchBar.Result>
            )}
          </SearchBar.Results>
        </SearchBar.DialogContent>
      </SearchBar.Dialog>
    </div>
  )
}
```

***

## Using a Readonly Token (recommended)

The token used in the `Search` client above is a read-only token.

<img />

This token is safe to expose on the frontend. This allows your application to perform search queries without the need for a backend API.

To use environment variables for the token, set it as `NEXT_PUBLIC_YOUR_READONLY_TOKEN` in your `.env` file.

Optionally, you can also create a separate backend API to handle search on the server.

***

## Handling Results

You can perform actions with the search results by using the `onSelect` prop on `SearchBar.Item`:

```tsx
<SearchBar.Result
  onSelect={() => {
    // 👇 do something with result
    console.log(result)
  }}
  value={result.id}
  key={result.id}
>
```

***

## Customization

This component is beautifully pre-styled, but 100% customizable. You can change every piece of it yourself by passing normal React props to each component (such as `className`).

**For example**: If you wanted to change the primary color, change the CSS classes:

```tsx
<SearchBar.Input
  className="focus:ring-red-500"
  placeholder="Type to search movies..."
/>

<SearchBar.ResultTitle
  className="font-medium text-gray-900"
  highlightClassName="decoration-red-500 text-red-500"
>
  {result.content.title}
</SearchBar.ResultTitle>
```

***

This component is based on the [Radix UI Dialog Primitive](https://www.radix-ui.com/primitives/docs/components/dialog) and Paco Coursey's [cmdk](https://cmdk.paco.me/) library.

# Delete Vectors
Source: https://upstash.com/docs/vector/api/endpoints/delete

You can delete one or more vectors by providing their vector ids,
vector id prefix, or metadata filter.

<Tip>
  Vectors will be deleted from the default namespace by default.
  You can use a different namespace by specifying it in the request path.
</Tip>

## Request

<ParamField body="ids" type="string[]">
  Array of vector ids to delete.
</ParamField>

<ParamField body="prefix" type="string">
  Prefix of vector ids to delete.
</ParamField>

<ParamField body="filter" type="string">
  [Metadata filter](/docs/vector/features/filtering) for the vectors to delete.
  <Warning>Deleting vectors with metadata filter is a O(N) operation that performs a full scan.
  Therefore, it might be slow for large indexes.</Warning>
</ParamField>

## Path

<ParamField path="namespace" type="string" default="">
  The namespace to use.
  When no namespace is specified, the default namespace will be used.
</ParamField>

## Response

<ResponseField name="deleted" type="number">
  The number of the successfully deleted vectors.
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/delete \
  -X DELETE \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{ "ids": [ "id-0", "id-1" ] }'
```

```sh curl (Namespace)
curl $UPSTASH_VECTOR_REST_URL/delete/ns \
  -X DELETE \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{ "ids": [ "id-0", "id-1" ] }'
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
    "result": {
        "deleted": 2
    }
}
```

</ResponseExample>

# Delete Namespace
Source: https://upstash.com/docs/vector/api/endpoints/delete-namespace

<Note>
  The default namespace, which is the empty string `""`, cannot be deleted.
</Note>

## Request

This endpoint doesn't require any additional data.

## Path

<ParamField path="namespace" type="string" required>
  The namespace to delete.
</ParamField>

## Response

<ResponseField name="result" type="string">
  `"Success"` string.
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/delete-namespace/ns \
  -X DELETE \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN"
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
    "result": "Success"
}
```

```json 404 Not Found
{
    "error": "Namespace ns for the index $NAME does not exist",
    "status": 404
}
```

</ResponseExample>

# Fetch Vectors
Source: https://upstash.com/docs/vector/api/endpoints/fetch

You can fetch vector values or metadata of one or more by providing
their vector ids or id prefix.

<Tip>
  Vectors will be fetched from the default namespace by default.
  You can use a different namespace by specifying it in the request path.
</Tip>

## Request

<ParamField body="ids" type="string[]">
  Array of vector ids to fetch.
</ParamField>

<ParamField body="prefix" type="string">
  Prefix of vector ids to fetch.

  <Info>When you fetch vectors with an id prefix, at most `1000` vectors will be returned.
  If there are more vectors, please use the [range](./range) API with an id prefix.</Info>
</ParamField>

<ParamField body="includeMetadata" type="boolean" default="false">
  Whether to include the metadata of the vectors in the response, if any.
  It is recommended to set this to `true` to easily identify vectors.
</ParamField>
<ParamField body="includeVectors" type="boolean" default="false">
  Whether to include the vector values in the response.
  It is recommended to set this to `false` as the vector values can be
  quite big, and not needed most of the time.
</ParamField>
<ParamField body="includeData" type="boolean" default="false">
  Whether to include the data of the vectors in the response, if any.
</ParamField>

## Path

<ParamField path="namespace" type="string" default="">
  The namespace to use.
  When no namespace is specified, the default namespace will be used.
</ParamField>

## Response

<ResponseField name="Vectors" type="Object[]">
  Array of vectors in the same order they provided in the ids array.
  Array elements can be `null` if no such vector exists with the provided id.
  <Expandable defaultOpen="true">
    <ResponseField name="id" type="string" required>
      The id of the vector.
    </ResponseField>
    <ResponseField name="vector" type="number[]">
      The dense vector value for dense and hybrid indexes.
    </ResponseField>
    <ResponseField name="sparseVector" type="Object[]">
      The sparse vector value for sparse and hybrid indexes.
      <Expandable defaultOpen="true">
        <ResponseField name="indices" type="number[]">
          Indices of the non-zero valued dimensions.
        </ResponseField>
        <ResponseField name="values" type="number[]">
          Values of the non-zero valued dimensions.
        </ResponseField>
      </Expandable>
    </ResponseField>
    <ResponseField name="metadata" type="Object">
      The metadata of the vector, if any.
    </ResponseField>
    <ResponseField name="data" type="string">
      The unstructured data of the vector, if any.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/fetch \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{ "ids": ["id-0"], "includeMetadata": true }'
```

```sh curl (Namespace)
curl $UPSTASH_VECTOR_REST_URL/fetch/ns \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{ "ids": ["id-0", "id-1"], "includeMetadata": true }'
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
    "result": [
        {
            "id": "id-0",
            "metadata": {
                "link": "upstash.com"
            }
        },
        {
            "id": "id-1"
        }
    ]
}
```

</ResponseExample>

# Fetch Random Vector
Source: https://upstash.com/docs/vector/api/endpoints/fetch-random

## Request

This endpoint doesn't require any additional data.

## Path

<ParamField path="namespace" type="string" default="">
  The namespace to use.
  When no namespace is specified, the default namespace will be used.
</ParamField>

## Response

The response will be `null` if the namespace is empty.

<ResponseField name="id" type="string" required>
  The id of the vector.
</ResponseField>
<ResponseField name="vector" type="number[]">
  The dense vector value for dense and hybrid indexes.
</ResponseField>
<ResponseField name="sparseVector" type="Object[]">
  The sparse vector value for sparse and hybrid indexes.
  <Expandable defaultOpen="true">
    <ResponseField name="indices" type="number[]">
      Indices of the non-zero valued dimensions.
    </ResponseField>
    <ResponseField name="values" type="number[]">
      Values of the non-zero valued dimensions.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/random \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN"
```

```sh curl (Namespace)
curl $UPSTASH_VECTOR_REST_URL/random/ns \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN"
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
    "result": {
        "id": "id-0",
        "vector": [0.1, 0.2]
    }
}
```

</ResponseExample>

# Index Info
Source: https://upstash.com/docs/vector/api/endpoints/info

Info will be updated eventually, so it might take some time to see the effect of changes in this endpoint.

## Request

This request doesn't require any additional data.

## Response

<ResponseField name="vectorCount" type="number" required>
  The number of vectors in the index, that are ready to use. This is the total
  number of vectors across all namespaces.
</ResponseField>
<ResponseField name="pendingVectorCount" type="number" required>
  The number of vectors in the index, that are still processing and not ready to
  use. This is the total number of pending vectors across all namespaces.
</ResponseField>
<ResponseField name="indexSize" type="number" required>
  The total size of the index, in **bytes**.
</ResponseField>
<ResponseField name="dimension" type="number" required>
  Dimension of the vectors.
</ResponseField>
<ResponseField name="similarityFunction" type="string" required>
  Name of the similarity function used in indexing and queries.
</ResponseField>

<ResponseField name="indexType" type="string" required>
  Type of the index. Possible values: `"DENSE"`, `"SPARSE"`, `"HYBRID"`
</ResponseField>

<ResponseField name="denseIndex" type="object">
  Information about the dense vector index configuration.
  <Expandable>
    <ResponseField name="dimension" type="number" required>
      Dimension of the dense vectors.
    </ResponseField>
    <ResponseField name="similarityFunction" type="string" required>
      Similarity function used for dense vector comparisons.
      Possible values: `"COSINE"`, `"EUCLIDEAN"`, `"DOT_PRODUCT"`
    </ResponseField>
    <ResponseField name="embeddingModel" type="string" required>
      Name of the embedding model used for dense vectors.
    </ResponseField>
  </Expandable>
</ResponseField>
<ResponseField name="sparseIndex" type="object">
  Information about the sparse vector index configuration.
  <Expandable>
    <ResponseField name="embeddingModel" type="string" required>
      Name of the embedding model used for sparse vectors.
    </ResponseField>
  </Expandable>
</ResponseField>
<ResponseField name="namespaces" type="object" required>
  Map of namespace names to namespace .

  <Note>Every index has at least one namespace called default namespace, whose name is the empty string `""`.</Note>
  <Expandable defaultOpen="true">
    <ResponseField name="vectorCount" type="number" required>
      The number of vectors in the namespace, that are ready to use.
    </ResponseField>
    <ResponseField name="pendingVectorCount" type="number" required>
      The number of vectors in the namespace, that are still processing
      and not ready to use.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/info \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN"
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
  "result": {
    "vectorCount": 7,
    "pendingVectorCount": 0,
    "indexSize": 43501,
    "dimension": 1024,
    "similarityFunction": "COSINE",
    "indexType": "HYBRID",
    "denseIndex": {
      "dimension": 1024,
      "similarityFunction": "COSINE",
      "embeddingModel": "BGE_M3"
    },
    "sparseIndex": {
      "embeddingModel": "BM25"
    },
    "namespaces": {
      "": {
        "vectorCount": 6,
        "pendingVectorCount": 0
      },
      "ns": {
        "vectorCount": 1,
        "pendingVectorCount": 0
      }
    }
  }
}
```

</ResponseExample>

# List Namespaces
Source: https://upstash.com/docs/vector/api/endpoints/list-namespaces

## Request

This endpoint doesn't require any additional data.

## Response

<ResponseField name="namespaces" type="string[]" required>
  Array of namespace names.

  <Note>Every index has at least one namespace called default namespace, whose name is the empty string `""`.</Note>
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/list-namespaces \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN"
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
    "result": ["", "ns0", "ns1"]
}
```

</ResponseExample>

# Query Vectors
Source: https://upstash.com/docs/vector/api/endpoints/query

<Tip>
  Query will run against the default namespace by default.
  You can use a different namespace by specifying it in the request path.
</Tip>

## Request

It is also possible to send a batch query request by providing an array
of fields below.

<ParamField body="vector" name="vector" type="number[]" required>
  The query vector
  <Note>The query vector should have the same dimensions as your index.</Note>
</ParamField>

<ParamField body="topK" type="number" default="10">
  The total number of the vectors that you want to receive as a query
  result. The response will be sorted based on the distance metric score,
  and at most `topK` many vectors will be returned.
</ParamField>
<ParamField body="includeMetadata" type="boolean" default="false">
  Whether to include the metadata of the vectors in the response, if any.
  It is recommended to set this to `true` to easily identify vectors.
</ParamField>
<ParamField body="includeVectors" type="boolean" default="false">
  Whether to include the vector values in the response.
  It is recommended to set this to `false` as the vector values can be
  quite big, and not needed most of the time.
</ParamField>
<ParamField body="includeData" type="boolean" default="false">
  Whether to include the data of the vectors in the response, if any.
</ParamField>
<ParamField body="filter" type="string" default="">
  [Metadata filter](/docs/vector/features/filtering) to apply.
</ParamField>
<ParamField body="weightingStrategy" type="string">
  For sparse vectors of sparse and hybrid indexes, specifies what kind of
  weighting strategy should be used while querying the matching non-zero
  dimension values of the query vector with the documents.

  If not provided, no weighting will be used.

  Only possible value is `IDF` (inverse document frequency).
</ParamField>
<ParamField body="fusionAlgorithm" type="string">
  Fusion algorithm to use while fusing scores
  from dense and sparse components of a hybrid index.

  If not provided, defaults to `RRF` (Reciprocal Rank Fusion).

  Other possible value is `DBSF` (Distribution-Based Score Fusion).
</ParamField>

## Path

<ParamField path="namespace" type="string" default="">
  The namespace to use.
  When no namespace is specified, the default namespace will be used.
</ParamField>

## Response

If the request was an array of a single element, or a JSON object,
an object with the following fields is returned.

If the request was an array of more than one items, an array of
objects below is returned, one for each query item.

<Note>
  For dense indexes, the score is normalized to always be between 0 and 1.
  The closer the score is to 1, the more similar the vector is to the query vector.
  This does not depend on the distance metric you use.

  For sparse and hybrid indexes, scores can be arbitrary values, but the score
  will be higher for more similar vectors.
</Note>

<ResponseField name="Scores" type="Object[]">
  <Expandable defaultOpen="true">
    <ResponseField name="id" type="string" required>
      The id of the vector.
    </ResponseField>
    <ResponseField name="score" type="number" required>
      The similarity score of the vector, calculated based on the distance metric of your index.
    </ResponseField>
    <ResponseField name="vector" type="number[]">
      The dense vector value for dense and hybrid indexes.
    </ResponseField>
    <ResponseField name="sparseVector" type="Object[]">
      The sparse vector value for sparse and hybrid indexes.
      <Expandable defaultOpen="true">
        <ResponseField name="indices" type="number[]">
          Indices of the non-zero valued dimensions.
        </ResponseField>
        <ResponseField name="values" type="number[]">
          Values of the non-zero valued dimensions.
        </ResponseField>
      </Expandable>
    </ResponseField>
    <ResponseField name="metadata" type="Object">
      The metadata of the vector, if any.
    </ResponseField>
    <ResponseField name="data" type="string">
      The unstructured data of the vector, if any.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/query \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{ "vector": [0.1, 0.2], "topK": 2, "includeMetadata": true }'
```

```sh curl (Namespace)
curl $UPSTASH_VECTOR_REST_URL/query/ns \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{ "vector": [0.1, 0.2], "topK": 2, "includeMetadata": true }'
```

```sh curl (Batch Query)
curl "$UPSTASH_VECTOR_REST_URL/query" \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '[
        {
          "vector": [0.1, 0.2],
          "topK": 2,
          "includeMetadata": true
        },
        {
          "vector": [0.2, 0.3],
          "topK": 3
        }
      ]'
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
    "result": [
        {
            "id": "id-0",
            "score": 1.0,
            "metadata": {
                "link": "upstash.com"
            }
        },
        {
            "id": "id-1",
            "score": 0.99996454
        }
    ]
}
```
</ResponseExample>

# Query Data
Source: https://upstash.com/docs/vector/api/endpoints/query-data

<Warning>
  To use this endpoint, the index must be created with an [embedding model](/docs/vector/features/embeddingmodels).
</Warning>

<Tip>
  Query will run against the default namespace by default.
  You can use a different namespace by specifying it in the request path.
</Tip>

## Request

It is also possible to send a batch query request by providing an array
of fields below.

<ParamField body="data" name="data" type="string" required>
  The raw text data to embed and query.
</ParamField>
<ParamField body="topK" type="number" required default="10">
  The total number of the vectors that you want to receive as a query
  result. The response will be sorted based on the distance metric score,
  and at most `topK` many vectors will be returned.
</ParamField>
<ParamField body="includeMetadata" type="boolean" default="false">
  Whether to include the metadata of the vectors in the response, if any.
  It is recommended to set this to `true` to easily identify vectors.
</ParamField>
<ParamField body="includeVectors" type="boolean" default="false">
  Whether to include the vector values in the response.
  It is recommended to set this to `false` as the vector values can be
  quite big, and not needed most of the time.
</ParamField>
<ParamField body="includeData" type="boolean" default="false">
  Whether to include the data of the vectors in the response.
  When set to true, data will contain the raw text data used
  while upserting.
</ParamField>
<ParamField body="filter" type="string" default="">
  [Metadata filter](/docs/vector/features/filtering) to apply.
</ParamField>
<ParamField body="weightingStrategy" type="string">
  For sparse vectors of sparse and hybrid indexes, specifies what kind of
  weighting strategy should be used while querying the matching non-zero
  dimension values of the query vector with the documents.

  If not provided, no weighting will be used.

  Only possible value is `IDF` (inverse document frequency).
</ParamField>
<ParamField body="fusionAlgorithm" type="string">
  Fusion algorithm to use while fusing scores
  from dense and sparse components of a hybrid index.

  If not provided, defaults to `RRF` (Reciprocal Rank Fusion).

  Other possible value is `DBSF` (Distribution-Based Score Fusion).
</ParamField>
<ParamField body="queryMode" type="string">
    Query mode for hybrid indexes with Upstash-hosted
    embedding models.

    Specifies whether to run the query in only the
    dense index, only the sparse index, or in both.

    If not provided, defaults to `HYBRID`.

    Possible values are `HYBRID`, `DENSE`, and `SPARSE`.
</ParamField>

## Path

<ParamField path="namespace" type="string" default="">
  The namespace to use.
  When no namespace is specified, the default namespace will be used.
</ParamField>

## Response

If the request was an array of a single element, or a JSON object,
an object with the following fields is returned.

If the request was an array of more than one items, an array of
objects below is returned, one for each query item.

<Note>
  For dense indexes, the score is normalized to always be between 0 and 1.
  The closer the score is to 1, the more similar the vector is to the query vector.
  This does not depend on the distance metric you use.

  For sparse and hybrid indexes, scores can be arbitrary values, but the score
  will be higher for more similar vectors.
</Note>

<ResponseField name="Scores" type="Object[]">
  <Expandable defaultOpen="true">
    <ResponseField name="id" type="string" required>
      The id of the vector.
    </ResponseField>
    <ResponseField name="score" type="number" required>
      The similarity score of the vector, calculated based on the distance metric of your index.
    </ResponseField>
    <ResponseField name="vector" type="number[]">
      The dense vector value for dense and hybrid indexes.
    </ResponseField>
    <ResponseField name="sparseVector" type="Object[]">
      The sparse vector value for sparse and hybrid indexes.
      <Expandable defaultOpen="true">
        <ResponseField name="indices" type="number[]">
          Indices of the non-zero valued dimensions.
        </ResponseField>
        <ResponseField name="values" type="number[]">
          Values of the non-zero valued dimensions.
        </ResponseField>
      </Expandable>
    </ResponseField>
    <ResponseField name="metadata" type="Object">
      The metadata of the vector, if any.
    </ResponseField>
    <ResponseField name="data" type="string">
      The textual data of the vector before embedding it.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/query-data \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{ "data": "What is Upstash?", "topK": 2, "includeMetadata": true }'
```

```sh curl (Namespace)
curl $UPSTASH_VECTOR_REST_URL/query-data/ns \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{ "data": "What is Upstash?", "topK": 2, "includeMetadata": true }'
```

```sh curl (Batch Query)
curl $UPSTASH_VECTOR_REST_URL/query-data \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '[
        {
          "data": "What is Upstash?",
          "topK": 2,
          "includeMetadata": true
        },
        {
          "data": "What is Upstash Vector?",
          "topK": 3
        }
      ]'
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
    "result": [
        {
            "id": "id-0",
            "score": 1.0,
            "metadata": {
                "link": "upstash.com"
            }
        },
        {
            "id": "id-1",
            "score": 0.99996454
        }
    ]
}
```

```json 422 Unprocessable Entity
{
    "error": "Embedding data for this index is not allowed. The index must be created with an embedding model to use it.",
    "status": 422
}
```

</ResponseExample>

# Range Vectors
Source: https://upstash.com/docs/vector/api/endpoints/range

<Tip>
  By default vectors from the default namespace will be iterated.
  You can use a different namespace by specifying it in the request path.
</Tip>

## Request

<ParamField body="cursor" type="string" required>
  The offset to the last retrieved vector. Should be set to `"0"` in the initial
  range.
</ParamField>
<ParamField body="prefix" type="string">
  Prefix of the vector ids to range over.
</ParamField>
<ParamField body="limit" type="number" required>
  The number of maximum vectors that you want in the response of range. (page
  size)
</ParamField>
<ParamField body="includeMetadata" type="boolean" default="false">
  Whether to include the metadata of the vectors in the response, if any.
  It is recommended to set this to `true` to easily identify vectors.
</ParamField>
<ParamField body="includeVectors" type="boolean" default="false">
  Whether to include the vector values in the response.
  It is recommended to set this to `false` as the vector values can be
  quite big, and not needed most of the time.
</ParamField>
<ParamField body="includeData" type="boolean" default="false">
  Whether to include the data of the vectors in the response, if any.
</ParamField>

## Path

<ParamField path="namespace" type="string" default="">
  The namespace to use.
  When no namespace is specified, the default namespace will be used.
</ParamField>

## Response

<ResponseField name="nextCursor" type="string" required>
  The offset for the next range. You should place this in the `cursor` field for
  the next range. It will be equal to empty string if there are no other vectors to range.
</ResponseField>

<ResponseField name="vectors" type="Object[]" required>
  <Expandable defaultOpen="true">
    <ResponseField name="id" type="string" required>
      The id of the vector.
    </ResponseField>
    <ResponseField name="vector" type="number[]">
      The dense vector value for dense and hybrid indexes.
    </ResponseField>
    <ResponseField name="sparseVector" type="Object[]">
      The sparse vector value for sparse and hybrid indexes.
      <Expandable defaultOpen="true">
        <ResponseField name="indices" type="number[]">
          Indices of the non-zero valued dimensions.
        </ResponseField>
        <ResponseField name="values" type="number[]">
          Values of the non-zero valued dimensions.
        </ResponseField>
      </Expandable>
    </ResponseField>
    <ResponseField name="metadata" type="Object">
      The metadata of the vector, if any.
    </ResponseField>
    <ResponseField name="data" type="string">
      The unstructured data of the vector, if any.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/range \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{ "cursor": "0", "limit": 2, "includeMetadata": true }'
```

```sh curl (Namespace)
curl $UPSTASH_VECTOR_REST_URL/range/ns \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{ "cursor": "0", "limit": 2, "includeMetadata": true }'
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
    "result": {
        "nextCursor": "2",
        "vectors": [
            {
                "id": "id-0",
                "metadata": {
                    "link": "upstash.com"
                }
            },
            {
                "id": "id-1"
            }
        ]
    }
}
```

</ResponseExample>

# Rename Namespace
Source: https://upstash.com/docs/vector/api/endpoints/rename-namespace

<Note>
  The default namespace, which is the empty string `""`, cannot be renamed.
</Note>

<Note>
  There should not be a namespace with the given new namespace name, unless the
  delete existing flag is passed.
</Note>

## Request

<ParamField body="namespace" type="string" required>
  The name of the namespace to rename.
</ParamField>

<ParamField body="newNamespace" type="string" required>
  The new name of the namespace.
</ParamField>

<ParamField body="deleteExisting" type="boolean" default="false">
  When the delete existing flag is passed, if there exists a namespace
  with the new namespace name, it is deleted before the rename operation.
</ParamField>

## Response

<ResponseField name="renamed" type="boolean">
  Whether the namespace is renamed or not.
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/rename-namespace \
  -X POST \
  -d '{ "namespace": "ns", "newNamespace": "newNs" }' \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN"
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
    "result": { "renamed": true }
}
```

</ResponseExample>

# Reset Namespace(s)
Source: https://upstash.com/docs/vector/api/endpoints/reset

The namespace will be completely empty after `/reset` is called, but will not be deleted.

<Tip>
  Reset operation will be performed against the default namespace by default.
  You can use a different namespace by specifying it in the request path.
</Tip>

## Request

This request doesn't require any additional data.

## Query

<ParamField query="all" type="string">
  When given, resets all namespaces of an index.
</ParamField>

## Path

<ParamField path="namespace" type="string" default="">
  The namespace to use.
  When no namespace is specified, the default namespace will be used.
</ParamField>

## Response

<ResponseField name="result" type="string">
  `"Success"` string.
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/reset \
  -X DELETE \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN"
```

```sh curl (Namespace)
curl $UPSTASH_VECTOR_REST_URL/reset/ns \
  -X DELETE \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN"
```

```sh curl (All Namespaces)
curl $UPSTASH_VECTOR_REST_URL/reset?all \
  -X DELETE \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN"
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
    "result": "Success"
}
```

</ResponseExample>

# Resume
Source: https://upstash.com/docs/vector/api/endpoints/resumable-query/resume

## Request

<ParamField body="uuid" type="string" required>
  The unique identifier returned from the start resumable query request.
</ParamField>

<ParamField body="additionalK" type="number" required>
  The number of additional results to fetch.
</ParamField>

## Response

<ResponseField name="Scores" type="Object[]">
  <Expandable defaultOpen="true">
    <ResponseField name="id" type="string" required>
      The id of the vector.
    </ResponseField>
    <ResponseField name="score" type="number" required>
      The similarity score of the vector, calculated based on the distance
      metric of your index.
    </ResponseField>
    <ResponseField name="vector" type="number[]">
      The dense vector value for dense and hybrid indexes.
    </ResponseField>
    <ResponseField name="sparseVector" type="Object[]">
      The sparse vector value for sparse and hybrid indexes.
      <Expandable defaultOpen="true">
        <ResponseField name="indices" type="number[]">
          Indices of the non-zero valued dimensions.
        </ResponseField>
        <ResponseField name="values" type="number[]">
          Values of the non-zero valued dimensions.
        </ResponseField>
      </Expandable>
    </ResponseField>
    <ResponseField name="metadata" type="Object">
      The metadata of the vector, if any.
    </ResponseField>
    <ResponseField name="data" type="string">
      The unstructured data of the vector, if any.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/resumable-query-next \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{
    "uuid": "550e8400-e29b-41d4-a716-446655440000",
    "additionalK": 2
  }'
```

</RequestExample>

# Start with Data
Source: https://upstash.com/docs/vector/api/endpoints/resumable-query/start-with-data

## Request

<ParamField body="data" type="string" required>
  The text data to be embedded and used for querying.
</ParamField>

<ParamField body="topK" type="number" default="10">
  The total number of the vectors that you want to receive as a query result.
  The response will be sorted based on the distance metric score, and at most
  `topK` many vectors will be returned.
</ParamField>

<ParamField body="includeMetadata" type="boolean" default="false">
  Whether to include the metadata of the vectors in the response, if any. It is
  recommended to set this to `true` to easily identify vectors.
</ParamField>

<ParamField body="includeVectors" type="boolean" default="false">
  Whether to include the vector values in the response. It is recommended to set
  this to `false` as the vector values can be quite big, and not needed most of
  the time.
</ParamField>

<ParamField body="includeData" type="boolean" default="false">
  Whether to include the data of the vectors in the response, if any.
</ParamField>

<ParamField body="filter" type="string" default="">
  [Metadata filter](/docs/vector/features/filtering) to apply.
</ParamField>

<ParamField body="maxIdle" type="number">
  Maximum idle time for the resumable query in seconds.
</ParamField>

<ParamField body="weightingStrategy" type="string">
  For sparse vectors of sparse and hybrid indexes, specifies what kind of
  weighting strategy should be used while querying the matching non-zero
  dimension values of the query vector with the documents.

  If not provided, no weighting will be used.

  Only possible value is `IDF` (inverse document frequency).
</ParamField>

<ParamField body="fusionAlgorithm" type="string">
  Fusion algorithm to use while fusing scores
  from dense and sparse components of a hybrid index.

  If not provided, defaults to `RRF` (Reciprocal Rank Fusion).

  Other possible value is `DBSF` (Distribution-Based Score Fusion).
</ParamField>

<ParamField body="queryMode" type="string">
    Query mode for hybrid indexes with Upstash-hosted
    embedding models.

    Specifies whether to run the query in only the
    dense index, only the sparse index, or in both.

    If not provided, defaults to `HYBRID`.

    Possible values are `HYBRID`, `DENSE`, and `SPARSE`.
</ParamField>

## Path

<ParamField path="namespace" type="string" default="">
  The namespace to use. When no namespace is specified, the default namespace
  will be used.
</ParamField>

## Response

Same as Resumable Query.

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/resumable-query-data \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{
    "data": "Hello world",
    "topK": 2,
    "includeMetadata": true,
    "maxIdle": 3600
  }'
```

</RequestExample>

# Start with Vector
Source: https://upstash.com/docs/vector/api/endpoints/resumable-query/start-with-vector

<Tip>
  Resumable queries allow you to fetch results in batches, which is useful for
  large result sets or when you want to implement pagination.
</Tip>

## Request

<ParamField body="vector" type="number[]" required>
  The query vector
  <Note>The query vector should have the same dimensions as your index.</Note>
</ParamField>

<ParamField body="topK" type="number" default="10">
  The total number of the vectors that you want to receive as a query result.
  The response will be sorted based on the distance metric score, and at most
  `topK` many vectors will be returned.
</ParamField>

<ParamField body="includeMetadata" type="boolean" default="false">
  Whether to include the metadata of the vectors in the response, if any. It is
  recommended to set this to `true` to easily identify vectors.
</ParamField>

<ParamField body="includeVectors" type="boolean" default="false">
  Whether to include the vector values in the response. It is recommended to set
  this to `false` as the vector values can be quite big, and not needed most of
  the time.
</ParamField>

<ParamField body="includeData" type="boolean" default="false">
  Whether to include the data of the vectors in the response, if any.
</ParamField>

<ParamField body="filter" type="string" default="">
  [Metadata filter](/docs/vector/features/filtering) to apply.
</ParamField>

<ParamField body="maxIdle" type="number">
  Maximum idle time for the resumable query in seconds.
</ParamField>

<ParamField body="weightingStrategy" type="string">
  For sparse vectors of sparse and hybrid indexes, specifies what kind of
  weighting strategy should be used while querying the matching non-zero
  dimension values of the query vector with the documents.

  If not provided, no weighting will be used.

  Only possible value is `IDF` (inverse document frequency).
</ParamField>

<ParamField body="fusionAlgorithm" type="string">
  Fusion algorithm to use while fusing scores
  from dense and sparse components of a hybrid index.

  If not provided, defaults to `RRF` (Reciprocal Rank Fusion).

  Other possible value is `DBSF` (Distribution-Based Score Fusion).
</ParamField>

## Path

<ParamField path="namespace" type="string" default="">
  The namespace to use. When no namespace is specified, the default namespace
  will be used.
</ParamField>

## Response

<ResponseField name="uuid" type="string" required>
  A unique identifier for the resumable query.
</ResponseField>

<ResponseField name="scores" type="Object[]">
  <Expandable defaultOpen="true">
    <ResponseField name="id" type="string" required>
      The id of the vector.
    </ResponseField>
    <ResponseField name="score" type="number" required>
      The similarity score of the vector, calculated based on the distance
      metric of your index.
    </ResponseField>
    <ResponseField name="vector" type="number[]">
      The dense vector value for dense and hybrid indexes.
    </ResponseField>
    <ResponseField name="sparseVector" type="Object[]">
      The sparse vector value for sparse and hybrid indexes.
      <Expandable defaultOpen="true">
        <ResponseField name="indices" type="number[]">
          Indices of the non-zero valued dimensions.
        </ResponseField>
        <ResponseField name="values" type="number[]">
          Values of the non-zero valued dimensions.
        </ResponseField>
      </Expandable>
    </ResponseField>
    <ResponseField name="metadata" type="Object">
      The metadata of the vector, if any.
    </ResponseField>
    <ResponseField name="data" type="string">
      The unstructured data of the vector, if any.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/resumable-query \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{
    "vector": [0.1, 0.2],
    "topK": 2,
    "includeMetadata": true,
    "maxIdle": 3600
  }'
```

```sh curl (Namespace)
curl $UPSTASH_VECTOR_REST_URL/resumable-query/ns \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{
    "vector": [0.1, 0.2],
    "topK": 2,
    "includeMetadata": true,
    "maxIdle": 3600
  }'
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "scores": [
    {
      "id": "id-0",
      "score": 1.0,
      "metadata": {
        "link": "upstash.com"
      }
    },
    {
      "id": "id-1",
      "score": 0.99996454
    }
  ]
}
```

</ResponseExample>

# Stop Resumable Query
Source: https://upstash.com/docs/vector/api/endpoints/resumable-query/stop

## Request

<ParamField body="uuid" type="string" required>
  The unique identifier of the resumable query to end.
</ParamField>

## Response

<ResponseField name="result" type="string">
  A success message indicating the query was ended.
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/resumable-query-end \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{
    "uuid": "550e8400-e29b-41d4-a716-446655440000"
  }'
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
  "result": "Success"
}
```

</ResponseExample>

# Update Vector
Source: https://upstash.com/docs/vector/api/endpoints/update

<Tip>
  The vector will be updated int the default namespace by default.
  You can use a different namespace by specifying it in the request path.
</Tip>

## Request

You can update a vector value, data, or metadata; or any combination
of those.

<ParamField body="id" type="string" required>
  The id of the vector.
</ParamField>
<ParamField body="vector" type="number[]">
  The dense vector value to update to for dense and hybrid indexes.
  <Note>The vector should have the same dimensions as your index.</Note>
</ParamField>
<ParamField body="sparseVector" type="Object[]">
  The sparse vector value to update to for sparse and hybrid indexes.
  <Expandable defaultOpen="true">
    <ResponseField name="indices" type="number[]">
      Indices of the non-zero valued dimensions.
    </ResponseField>
    <ResponseField name="values" type="number[]">
      Values of the non-zero valued dimensions.
    </ResponseField>
  </Expandable>
</ParamField>
<ParamField body="data" type="string">
  The raw text data to update to.
  <Note>If the index is created with an [embedding model](/docs/vector/features/embeddingmodels)
      this will embed the data into a vector and will also update the vector, along with data.</Note>
</ParamField>
<ParamField body="metadata" type="Object">
  The metadata to update to.
</ParamField>
<ParamField body="metadataUpdateMode" type="string">
  Whether to overwrite the whole metadata while updating
  it, or patch the metadata (insert new fields or update or delete existing fields)
  according to the `RFC 7396 JSON Merge Patch` algorithm.

  `OVERWRITE` for overwrite, `PATCH` for patch.
</ParamField>

<Note>
For hybrid indexes either none or both of `vector` and `sparseVector` fields
must be present. It is not allowed to update only `vector` or `sparseVector`.
</Note>

## Path

<ParamField path="namespace" type="string" default="">
  The namespace to use.
  When no namespace is specified, the default namespace will be used.
</ParamField>

## Response

<ResponseField name="updated" type="number">
  `1` if any vector is updated, `0` otherwise.
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/update \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{ "id": "id-1", "metadata": { "link": "upstash.com" } }'
```

```sh curl (Namespace)
curl $UPSTASH_VECTOR_REST_URL/update/ns \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{ "id": "id-2", "vector": [0.1, 0.2] }'
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
    "result": {
        "updated": 1
    }
}
```

</ResponseExample>

# Upsert Vectors
Source: https://upstash.com/docs/vector/api/endpoints/upsert

<Tip>
  The vector will be upserted into the default namespace by default.
  You can use a different namespace by specifying it in the request path.
</Tip>

## Request

You can either upsert a single vector, or multiple vectors in an array.

<ParamField body="id" type="string" required>
  The id of the vector.
</ParamField>
<ParamField body="vector" type="number[]">
  The dense vector value for dense and hybrid indexes.
  <Note>The vector should have the same dimensions as your index.</Note>
</ParamField>
<ParamField body="sparseVector" type="Object[]">
  The sparse vector value for sparse and hybrid indexes.
  <Expandable defaultOpen="true">
    <ResponseField name="indices" type="number[]">
      Indices of the non-zero valued dimensions.
    </ResponseField>
    <ResponseField name="values" type="number[]">
      Values of the non-zero valued dimensions.
    </ResponseField>
  </Expandable>
</ParamField>
<ParamField body="metadata" type="Object">
  The metadata of the vector. This makes identifying vectors
  on retrieval easier and can be used to with filters on queries.
</ParamField>
<ParamField body="data" type="string">
  The data of the vector. This is an unstructured raw text
  data, which can be anything associated with this vector.
</ParamField>

<Note>
For dense indexes, only `vector` should be provided, and `sparseVector` should not be set.

For sparse indexes, only `sparseVector` should be provided, and `vector` should not be set.

For hybrid indexes both of `vector` and `sparseVector` must be present.
</Note>

## Path

<ParamField path="namespace" type="string" default="">
  The namespace to use.
  When no namespace is specified, the default namespace will be used.
</ParamField>

## Response

<ResponseField name="result" type="string">
  `"Success"` string.
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/upsert \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '[
    { "id": "id-0", "vector": [0.1, 0.2], "metadata": { "link": "upstash.com" } },
    { "id": "id-1", "vector": [0.2, 0.3] }
  ]'
```

```sh curl (Namespace)
curl $UPSTASH_VECTOR_REST_URL/upsert/ns \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{ "id": "id-2", "vector": [0.1, 0.2], "metadata": { "link": "upstash.com" } }'
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
    "result": "Success"
}
```

```json 422 Unprocessable Entity
{
    "error": "Invalid vector dimension: 2, expected: 256",
    "status": 422
}
```

</ResponseExample>

# Upsert Data
Source: https://upstash.com/docs/vector/api/endpoints/upsert-data

<Warning>
  To use this endpoint, the index must be created with an [embedding model](/docs/vector/features/embeddingmodels).
</Warning>

<Tip>
  Vector embedding of the raw text data will be upserted into the
  default namespace by default.
  You can use a different namespace by specifying it in the request path.
</Tip>

## Request

You can either upsert a single data, or multiple data in an array.

<ParamField body="id" type="string" required>
  The id of the vector.
</ParamField>
<ParamField body="data" type="string" required>
  The raw text data to embed and upsert.
</ParamField>
<ParamField body="metadata" type="Object">
  The metadata of the vector. This makes identifying vectors
  on retrieval easier and can be used to with filters on queries.
</ParamField>

<Note>
  Data field of the vector will be automatically set to the
  raw text data, so that you can access it later, during
  queries.
</Note>

## Path

<ParamField path="namespace" type="string" default="">
  The namespace to use.
  When no namespace is specified, the default namespace will be used.
</ParamField>

## Response

<ResponseField name="result" type="string">
  `"Success"` string.
</ResponseField>

<RequestExample>

```sh curl
curl $UPSTASH_VECTOR_REST_URL/upsert-data \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '[
    { "id": "id-0", "data": "Upstash is a serverless data platform.", "metadata": { "link": "upstash.com" } },
    { "id": "id-1", "data": "Upstash Vector is a serverless vector database." }
  ]'
```

```sh curl (Namespace)
curl $UPSTASH_VECTOR_REST_URL/upsert-data/ns \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{ "id": "id-2", "data": "Upstash is a serverless data platform.", "metadata": { "link": "upstash.com" } }'
```

</RequestExample>

<ResponseExample>

```json 200 OK
{
    "result": "Success"
}
```

```json 422 Unprocessable Entity
{
    "error": "Embedding data for this index is not allowed. The index must be created with an embedding model to use it.",
    "status": 422
}
```

</ResponseExample>

# Getting Started
Source: https://upstash.com/docs/vector/api/get-started

If you do not have a vector database already, follow [these steps](/docs/vector/overall/getstarted) to create one.

In the database details section of the Upstash Console, scroll down to `Connect` section and select the `cURL` tab. You can simply copy the curl expression and run on your terminal.

```shell
curl $UPSTASH_VECTOR_REST_URL/upsert \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"id": "id-0", "vector": [0.87, 0.99]}'
```

## Response

REST API returns a JSON response. When command execution is successful, response JSON will have a single result field and its value will contain the Redis response.

Example:

```json
{ "result": "Success" }
```

When command execution is not successful, response JSON will have a single error field and its value will contain the error message.

Example:

```json
{
  "error": "Unauthorized: Invalid auth token",
  "status": 401
}
```

#### HTTP Response Codes

| Status Code              | Description                                                                                         |
| ------------------------ | --------------------------------------------------------------------------------------------------- |
| `200 OK`                 | When request is accepted and successfully executed.                                                 |
| `400 Bad Request`        | When there's a syntax error, an invalid/unsupported command is sent or command execution fails.     |
| `401 Unauthorized`       | When authentication fails; auth token is missing or invalid.                                        |
| `405 Method Not Allowed` | When an unsupported HTTP method is used. Only `HEAD`, `GET`, `POST`, and `PUT` methods are allowed. |

***

# Examples
Source: https://upstash.com/docs/vector/examples

A collection of examples, notebooks, and blog posts for building with Upstash Vector.

<CardGroup cols={2}>
  <Card
    title="Image Search"
    icon="python"
    href="https://colab.research.google.com/drive/1y7ZBBxzEOAeSRDhyOeTxIJADxBC-0Fue"
  >
    Image Similarity Search using CLIP

    `CLIP` `HuggingFace`
  </Card>
  <Card
    title="Learn Vector Stores"
    icon="python"
    href="https://colab.research.google.com/drive/1CpKUuPr1458sBZkqvL6Km_HBj-IS0fNa"
  >
    Learn How Vector Stores Work by Building a Recommendation Engine
  </Card>
  <Card
    title="RAG Chatbot"
    icon="python"
    href="https://colab.research.google.com/drive/1zSzb2lajFMxf6FDxDM0sUe5wpIx4KyU2"
  >
    RAG (Retrieval Augmented Generation) Chatbot with OpenAI and Upstash Vector

    `OpenAI` `text-embedding-ada-002`
  </Card>
  <Card
    title="Semantic Search with Sentence Transformers"
    icon="python"
    href="https://colab.research.google.com/drive/1NhqEzjKdAGfI-nhEa3pfqmBXWl2oN0Ve"
  >
    Semantic Search using Sentence Transformers and Upstash Vector

    `Sentence Transformers` `HuggingFace`
  </Card>
  <Card
    title="Semantic Search with BERT"
    icon="python"
    href="https://colab.research.google.com/drive/1xXSLtG2uItaBWAxXYgzwa5jo669mDk2p"
  >
    Semantic Search with DistilBERT on HuggingFace

    `BERT` `HuggingFace`
  </Card>
  <Card
    title="Semantic Search with OpenAI"
    icon="python"
    href="https://colab.research.google.com/drive/1WGSEe11RsG1_TwyQ09-lONOrEDwDLaC1"
  >
    Semantic Search with OpenAI and Upstash Vector

    `OpenAI` `text-embedding-ada-002`
  </Card>
  <Card
    title="API Examples"
    icon="python"
    href="https://colab.research.google.com/drive/1RLcGtNEkGrdj6f6yBK78cSrSdl6ht-5S"
  >
    Upstash Vector API Examples
  </Card>
  <Card
    title="Upsy"
    icon="js"
    href="https://github.com/upstash/upsy"
  >
    Upsy is a RAG based Slackbot which remembers all conversations.

    `OpenAI` `text-embedding-ada-002`
  </Card>
  <Card
    title="Semantic Search on Stackoverflow"
    icon="newspaper"
    href="https://upstash.com/blog/semantic-search-vector"
  >
    Blog: Semantic Search using all-MiniLM-L6-v2 model on HuggingFace

    `HuggingFace` `Sentence Transformers`
  </Card>
  <Card
    title="Image Similarity Search"
    icon="newspaper"
    href="https://upstash.com/blog/image-similarity-search"
  >
    Blog: Image Search using CLIP model on HuggingFace

    `HuggingFace` `CLIP`
  </Card>
</CardGroup>

# Algorithm
Source: https://upstash.com/docs/vector/features/algorithm

## Approximate Nearest Neighbor Search

The primary functionality of the vector store is straightforward: identifying the most similar vectors to a given vector.
While the concept is simple, translating it into a practical product poses significant challenges.

A simple and basic approach to searching in a vector database is to perform an exhaustive search by comparing a query vector to every other vector stored in the database one by one. However, this consumes too many resources and results in very high latencies, making it not very practical. To address this problem, Approximate Nearest Neighbor (`ANN`) algorithms are used. `ANN` search approximates the true nearest neighbor, which means it might not find the absolute closest point, but it will find one that's close enough, with a **low-latency** and by consuming **fewer resources**.
In the literature, the comparison of the results of `ANNS` with exhaustive search is called the recall rate.
The higher the recall rate the better the results.

Several `ANNS` algorithms, such as `HNSW`[1], `NSG`[2], and `DiskANN`[3], are available for use,
each with its distinct characteristics. One of the difficult problems in ANN algorithms is that indexing and querying vectors may require storing the whole data in memory. When the dataset is huge, then memory requirements for indexing may exceed available memory. `DiskANN` algorithm tries to solve this problem by using disk as the main storage for indexes and for performing queries directly on disk.`DiskANN` paper acknowledges that, if you try to store your vectors
in disk and use `HNSW` or `NSG`, you may end up with again very high latencies. `DiskANN` is focused
on serving queries from disk with **low-latency** and **good recall rate**.
And this helps Upstash Vector to be **cost-effective**, therefore cheaper compared to alternatives.

Even though `DiskANN` has its advantages, it also requires more work to be practical.
Main problem is that, you can't insert/update existing index without reindexing all the vectors.
For this problem, there is another improved paper `FreshDiskANN`[4]. `FreshDiskANN` improves `DiskANN` via introducing
a temporary index for up-to-date data in memory. Queries are served from both the temporary (up-to-date) index
and also from the disk. And these temporary indexes are merged to the disk from time-to-time behind the scene.

Upstash Vector is based on `DiskANN` and `FreshDiskANN` with more improvements based on our
tests and observations.

### References

1. Malkov, Y. A., Yashunin, D. A. (2016). _Efficient and Robust Approximate Nearest Neighbor Search Using Hierarchical Navigable Small World Graphs_. CoRR, abs/1603.09320 (2016). [https://arxiv.org/abs/1603.09320]
2. Fu, C., Xiang, C., Wang, C., Cai, D. (2019). _Fast Approximate Nearest Neighbor Search with Navigating Spreading-Out Graphs_. Proceedings of the VLDB, 12(5), 461–474. doi: 10.14778/3303753.3303754. [https://www.vldb.org/pvldb/vol12/p461-fu.pdf]
3. Subramanya, S. J., Devvrit, Kadekodi, R., Krishaswamy, R., Simhadri, H. V. (2019). _DiskANN: Fast Accurate Billion-Point Nearest Neighbor Search on a Single Node_. In Proceedings of the 33rd International Conference on Neural Information Processing Systems (NeurIPS '19), Article No.: 1233, Pages 13766–13776. [https://dl.acm.org/doi/abs/10.5555/3454287.3455520]
4. Singh, A., Subramanya, S. J., Krishnaswamy, R., Simhadri, H. V. (2021). _FreshDiskANN: A Fast and Accurate Graph-Based ANN Index for Streaming Similarity Search_. CoRR abs/2105.09613 (2021). [https://arxiv.org/abs/2105.09613]

# Embedding Models
Source: https://upstash.com/docs/vector/features/embeddingmodels

To store text in a vector database, it must first be converted into a vector,
also known as an embedding. Typically, this vectorization is done by a third
party.

By selecting an embedding model when you create your Upstash Vector database,
you can now upsert and query raw string data when using your database instead of
converting your text to a vector first. The vectorization is done automatically
by your selected model.

## Upstash Embedding Models - Video Guide

Let's look at how Upstash embeddings work, how the models we offer compare, and
which model is best for your use case.

<iframe
  id="intro-video"
  width='560'
  height='315'
  src='https://www.youtube.com/embed/aImBIYwn5Ew?rel=0&disablekb=1'
  title='YouTube video player'
  frameBorder='0'
  allow='accelerometer; fullscreen; clipboard-write; encrypted-media; gyroscope'
  allowFullScreen></iframe>

## Models

Upstash Vector comes with a variety of embedding models that score well in the
[MTEB](https://huggingface.co/spaces/mteb/leaderboard) leaderboard, a benchmark
for measuring the performance of embedding models. They support use cases such
as classification, clustering, or retrieval.

You can choose the following general purpose models for dense and hybrid indexes:

| Name                                                                                                    | Dimension | Sequence Length | MTEB  |
| ------------------------------------------------------------------------------------------------------- | --------- | --------------- | ----- |
| [BAAI/bge-large-en-v1.5](https://huggingface.co/BAAI/bge-large-en-v1.5)                                 | 1024      | 512             | 64.23 |
| [BAAI/bge-base-en-v1.5](https://huggingface.co/BAAI/bge-base-en-v1.5)                                   | 768       | 512             | 63.55 |
| [BAAI/bge-small-en-v1.5](https://huggingface.co/BAAI/bge-small-en-v1.5)                                 | 384       | 512             | 62.17 |
| [BAAI/bge-m3](https://huggingface.co/BAAI/bge-m3)                                                       | 1024      | 8192            | *     |

<Note>
  The sequence length is not a hard limit. Models truncate the input
  appropriately when given a raw text data that would result in more tokens than
  the given sequence length. However, we recommend using appropriate models and
  not exceeding their sequence length to have more accurate results.
</Note>

<Note>
  MTEB score for the `BAAI/bge-m3` is not fully measured.
</Note>

For sparse and hybrid indexes, on the following models can be selected:

| Name                                              |
| ------------------------------------------------- |
| [BAAI/bge-m3](https://huggingface.co/BAAI/bge-m3) |
| [BM25](https://en.wikipedia.org/wiki/Okapi_BM25)  |

See [Creating Sparse Vectors](/docs/vector/features/sparseindexes#creating-sparse-vectors) for the details of the above models.

## Using a Model

To start using embedding models, create the index with a model of your choice.

  <img />

Then, you can start upserting and querying raw text data without any extra
setup.

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.upsert(
    [("id-0", "Upstash is a serverless data platform.", {"field": "value"})],
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.upsert({
  id: "id-0",
  data: "Upstash is a serverless data platform.",
  metadata: {
    field: "value",
  },
})
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex("UPSTASH_VECTOR_REST_URL", "UPSTASH_VECTOR_REST_TOKEN")

	index.UpsertData(vector.UpsertData{
		Id:       "id-0",
		Data:     "Upstash is a serverless data platform.",
		Metadata: map[string]any{"field": "value"},
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\DataUpsert;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->upsertData(new DataUpsert(
  id: 'id-0',
  data: 'Upstash is a serverless data platform.',
  metadata: [
    'field' => 'value',
  ],
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/upsert-data \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"id": "1", "data": "Upstash is a serverless data platform.", "metadata": {"field": "value"}}'
```

</Tab>

</Tabs>

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.query(
    data="What is Upstash?",
    top_k=1,
    include_metadata=True,
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.query({
  data: "What is Upstash?",
  topK: 1,
  includeMetadata: true,
})
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex("UPSTASH_VECTOR_REST_URL", "UPSTASH_VECTOR_REST_TOKEN")

	index.QueryData(vector.QueryData{
		Data:            "What is Upstash?",
		TopK:            1,
		IncludeMetadata: true,
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\DataQuery;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->queryData(new DataQuery(
  data: 'What is Upstash?',
  topK: 1,
  includeMetadata: true,
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/query-data \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"data": "What is Upstash?", "topK": 1, "includeMetadata": "true"}'
```

</Tab>

</Tabs>

# Metadata Filtering
Source: https://upstash.com/docs/vector/features/filtering

You can further restrict the vector similarity search by providing a filter based on a specific metadata criteria.

Queries with metadata filters only return vectors which have metadata matching with the filter.

Upstash Vector allows you to filter keys which have the following value types:

* string
* number
* boolean
* object
* array

Filtering is implemented as a combination of in and post-filtering. Every query is assigned a filtering budget,
determining the number of candidate vectors that can be compared against the filter during query execution. If this
budget is exceeded, the system fallbacks into post-filtering. Therefore, with highly selective filters, fewer
than `topK` vectors may be returned.

## Filter Syntax

A filter has a syntax that resembles SQL, which consists of operators on object keys and boolean operators
to combine them.

Assuming you have a metadata like below:

```json
{
    "city": "Istanbul",
    "country": "Turkey",
    "is_capital": false,
    "population": 15460000,
    "geography": {
        "continent": "Asia",
        "coordinates": {
            "latitude": 41.0082,
            "longitude": 28.9784
        }
    },
    "economy": {
        "currency": "TRY",
        "major_industries": [
            "Tourism",
            "Textiles",
            "Finance"
        ]
    }
}
```

Then, you can query similar vectors with a filter like below:

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
  url="UPSTASH_VECTOR_REST_URL",
  token="UPSTASH_VECTOR_REST_TOKEN",
)

index.query(
  vector=[0.9215, 0.3897],
  filter="population >= 1000000 AND geography.continent = 'Asia'",
  top_k=5,
  include_metadata=True
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.query({
  vector: [0.9215, 0.3897],
  filter: "population >= 1000000 AND geography.continent = 'Asia'",
  topK: 5,
  includeMetadata: true,
});
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex("UPSTASH_VECTOR_REST_URL", "UPSTASH_VECTOR_REST_TOKEN")

	index.Query(vector.Query{
		Vector:          []float32{0.9215, 0.3897},
		Filter:          `population >= 1000000 AND geography.continent = 'Asia'`,
		TopK:            5,
		IncludeMetadata: true,
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorQuery;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->query(new VectorQuery(
  vector: [0.9215, 0.3897],
  topK: 5,
  includeMetadata: true,
  filter: "population >= 1000000 AND geography.continent = 'Asia'",
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/query \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{
   "vector":[0.9215,0.3897],
   "topK" : 5,
   "filter": "population >= 1000000 AND geography.continent = \"Asia\"",
   "includeMetadata": true
}'
```

</Tab>

</Tabs>

### Operators

#### Equals (=)

The `equals` operator filters keys whose values are equal to the given literal.

It is applicable to _string_, _number_, and _boolean_ values.

```SQL
country = 'Turkey' AND population = 15460000 AND is_capital = false
```

#### Not Equals (!=)

The `not equals` operator filters keys whose values are not equal to the given literal.

It is applicable to _string_, _number_, and _boolean_ values.

```SQL
country != 'Germany' AND population != 12500000 AND is_capital != true
```

#### Less Than (\<)

The `less than` operator filters keys whose values are less than the given literal.

It is applicable to _number_ values.

```SQL
population < 20000000 OR geography.coordinates.longitude < 30.0
```

#### Less Than or Equals (\<=)

The `less than or equals` operator filters keys whose values are less than or equal to the given literal.

It is applicable to _number_ values.

```SQL
population <= 20000000 OR geography.coordinates.longitude <= 30.0
```

#### Greater Than (>)

The `greater than` operator filters keys whose values are greater than the given literal.

It is applicable to _number_ values.

```SQL
population > 10000000 OR geography.coordinates.latitude > 39.5
```

#### Greater Than or Equals (>=)

The `greater than or equals` operator filters keys whose values are greater than or equal to the given literal.

It is applicable to _number_ values.

```SQL
population >= 10000000 OR geography.coordinates.latitude >= 39.5
```

#### Glob

The `glob` operator filters keys whose values match with the given UNIX glob pattern.

It is applicable to _string_ values.

It is a case sensitive operator.

The glob operator supports the following wildcards:

* `*` matches zero or more characters.
* `?` matches exactly one character.
* `[]` matches one character from the list
    * `[abc]` matches either `a`, `b`, or `c`.
    * `[a-z]` matches one of the range of characters from `a` to `z`.
    * `[^abc]` matches any one character other than `a`, `b`, or `c`.
    * `[^a-z]` matches any one character other than `a` to `z`.

For example, the filter below would only match with city names whose second character is `s` or `z`,
and ends with anything other than `m` to `z`.

```SQL
city GLOB '?[sz]*[^m-z]'
```

#### Not Glob

The `not glob` operator filters keys whose values do not match with the given UNIX glob pattern.

It is applicable to _string_ values.

It has the same properties with the glob operator.

For example, the filter below would only match with city names whose first character is anything other than `A`.

```SQL
city NOT GLOB 'A*'
```

#### In

The `in` operator filters keys whose values are equal to any of the given literals.

It is applicable to _string_, _number_, and _boolean_ values.

```SQL
country IN ('Germany', 'Turkey', 'France')
```

Semantically, it is equivalent to equals operator applied to all of the given literals with `OR` boolean operator in between:

```SQL
country = 'Germany' OR country = 'Turkey' OR country = 'France'
```

#### Not In

The `not in` operator filters keys whose values are not equal to any of the given literals.

It is applicable to _string_, _number_, and _boolean_ values.

```SQL
economy.currency NOT IN ('USD', 'EUR')
```

Semantically, it is equivalent to not equals operator applied to all of the given literals with `AND` boolean operator in between:

```SQL
economy.currency != 'USD' AND economy.currency != 'EUR'
```

#### Contains

The `contains` operator filters keys whose values contain the given literal.

It is applicable to _array_ values.

```SQL
economy.major_industries CONTAINS 'Tourism'
```

#### Not Contains

The `not contains` operator filters keys whose values do not contain the given literal.

It is applicable to _array_ values.

```SQL
economy.major_industries NOT CONTAINS 'Steel Production'
```

#### Has Field

The `has field` operator filters keys which have the given JSON field.

```SQL
HAS FIELD geography.coordinates
```

#### Has Not Field

The `has not field` operator filters keys which do not have the given JSON field.

```SQL
HAS NOT FIELD geography.coordinates.longitude
```

### Boolean Operators

Operators above can be combined with `AND` and `OR` boolean operators to form
compound filters.

```SQL
country = 'Turkey' AND population > 10000000
```

Boolean operators can be grouped with parentheses to have higher precedence.

```SQL
country = 'Turkey' AND (population > 10000000 OR is_capital = false)
```

When no parentheses are provided in ambiguous filters, `AND` will have higher
precedence than `OR`. So, the filter

```SQL
country = 'Turkey' AND population > 10000000 OR is_capital = false
```

would be equivalent to

```SQL
(country = 'Turkey' AND population > 10000000) OR is_capital = false
```

### Filtering Nested Objects

It is possible to filter nested object keys by referencing them with the `.` accessor.

Nested objects can be at arbitrary depths, so more than one `.` accessor can be used
in the same identifier.

```SQL
economy.currency != 'USD' AND geography.coordinates.latitude >= 35.0
```

### Filtering Array Elements

Apart from the `CONTAINS` and `NOT CONTAINS` operators, individual array elements can also
be filtered by referencing them with the `[]` accessor by their indexes.

Indexing is zero based.

```SQL
economy.major_industries[0] = 'Tourism'
```

Also, it is possible to index from the back using the `#` character with negative values.
`#` can be thought as the number of elements in the array, so `[#-1]` would reference the
last element.

```SQL
economy.major_industries[#-1] = 'Finance'
```

### Miscellaneous

* Identifiers (the left side of the operators) should be of the form `[a-zA-Z_][a-zA-Z_0-9.[\]#-]*`. In simpler terms, they should
start with characters from the English alphabet or `_`, and can continue with same characters plus numbers and other accessors
like `.`, `[0]`, or `[#-1]`.
* The string literals (strings in the right side of the operators) can be either single or double quoted.
* Boolean literals are represented as `1` or `0`.
* The operators, boolean operators, and boolean literals are case insensitive.

# Hybrid Indexes
Source: https://upstash.com/docs/vector/features/hybridindexes

Dense indexes are useful to perform semantic searches over
a dataset to find the most similar items quickly. It relies on the
embedding models to generate dense vectors that are similar to each
other for similar concepts. And, they do it well for the
data or the domain of the data that the embedding model is trained on.
But they sometimes fail, especially in the case where the data
is out of the training domain of the model. For such cases, a more traditional
exact search with sparse vectors performs better.

Hybrid indexes allow you to combine the best of these two worlds so that
you can get semantically similar results, and enhance them with exact
token/word matching to make the query results more relevant.

Upstash supports hybrid indexes that manage a dense and a sparse index
component for you. When you perform a query, it queries both the dense
and the sparse index and fuses the results.

## Creating Dense And Sparse Vectors

Since a hybrid index is a combination of a dense and a sparse index,
you can use the same methods you have used for dense and sparse indexes,
and combine them.

Upstash allows you to upsert and query dense and sparse vectors to
give you full control over the models you would use.

Also, to make embedding easier for you, Upstash provides some hosted
models and allows you to upsert and query text data. Behind the scenes,
the text data is converted to dense and sparse vectors.

You can create your index with a dense and sparse embedding model
to use this feature.

## Using Hybrid Indexes

### Upserting Dense and Sparse Vectors

You can upsert dense and sparse vectors into Upstash Vector indexes in two different ways.

#### Upserting Dense and Sparse Vectors

You can upsert dense and sparse vectors into the index as follows:

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index, Vector
from upstash_vector.types import SparseVector

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.upsert(
    vectors=[
        Vector(id="id-0", vector=[0.1, 0.5], sparse_vector=SparseVector([1, 2], [0.1, 0.2])),
        Vector(id="id-1", vector=[0.3, 0.7], sparse_vector=SparseVector([123, 44232], [0.5, 0.4])),
    ]
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.upsert([{
  id: 'id-0',
  vector: [0.1, 0.5],
  sparseVector: {
    indices: [2, 3],
    values: [0.13, 0.87],
  },
}])
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex(
		"UPSTASH_VECTOR_REST_URL",
		"UPSTASH_VECTOR_REST_TOKEN",
	)

	err := index.UpsertMany([]vector.Upsert{
		{
			Id:     "id-0",
			Vector: []float32{0.1, 0.5},
			SparseVector: &vector.SparseVector{
				Indices: []int32{1, 2},
				Values:  []float32{0.1, 0.2},
			},
		},
		{
			Id:     "id-1",
			Vector: []float32{0.3, 0.7},
			SparseVector: &vector.SparseVector{
				Indices: []int32{123, 44232},
				Values:  []float32{0.5, 0.4},
			},
		},
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorUpsert;
use Upstash\Vector\SparseVector;

use function Upstash\Vector\createRandomVector;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->upsert(new VectorUpsert(
  id: 'id-0',
  vector: createRandomVector(384),
  sparseVector: new SparseVector(
    indices: [1, 2, 3],
    values: [5, 6, 7],
  ),
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/upsert \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '[
    {"id": "id-0", "vector": [0.1, 0.5], "sparseVector": {"indices": [1, 2], "values": [0.1, 0.2]}},
    {"id": "id-1", "vector": [0.3, 0.7], "sparseVector": {"indices": [123, 44232], "values": [0.5, 0.4]}}
  ]'
```

</Tab>

</Tabs>

Note that, for hybrid indexes, you have to provide both dense and sparse
vectors. You can't omit one or both.

#### Upserting Text Data

If you created the hybrid index with Upstash-hosted dense and sparse embedding models,
you can upsert text data, and Upstash can embed it behind the scenes.

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index, Vector

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.upsert(
    vectors=[
        Vector(id="id-0", data="Upstash Vector provides dense and sparse embedding models."),
        Vector(id="id-1", data="You can upsert text data with these embedding models."),
    ]
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.upsert([
  {
    id: 'id-0',
    data: "Upstash Vector provides dense and sparse embedding models.",
  }
])
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex(
		"UPSTASH_VECTOR_REST_URL",
		"UPSTASH_VECTOR_REST_TOKEN",
	)

	err := index.UpsertData(vector.UpsertData{
		Id:   "id-0",
		Data: "Upstash Vector provides dense and sparse embedding models.",
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\DataUpsert;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->upsertData(new DataUpsert(
  id: 'id-0',
  data: 'Upstash Vector provides dense and sparse embedding models.',
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/upsert-data \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '[
    {"id": "id-0", "data": "Upstash Vector provides dense and sparse embedding models."},
    {"id": "id-1", "data": "You can upsert text data with these embedding models."}
  ]'
```

</Tab>

</Tabs>

### Querying Dense and Sparse Vectors

Similar to upserts, you can query dense and sparse vectors in two different ways.

#### Querying with Dense and Sparse Vectors

Hybrid indexes can be queried by providing dense and sparse vectors.

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index
from upstash_vector.types import SparseVector

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.query(
    vector=[0.5, 0.4],
    sparse_vector=SparseVector([3, 5], [0.3, 0.5]),
    top_k=5,
    include_metadata=True,
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.query({
  vector: [0.5, 0.4],
  sparseVector: {
    indices: [2, 3],
    values: [0.13, 0.87],
  },
  includeData: true,
  topK: 3,
})
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex(
		"UPSTASH_VECTOR_REST_URL",
		"UPSTASH_VECTOR_REST_TOKEN",
	)

	scores, err := index.Query(vector.Query{
		Vector: []float32{0.5, 0.4},
		SparseVector: &vector.SparseVector{
			Indices: []int32{3, 5},
			Values:  []float32{0.3, 05},
		},
		TopK:            5,
		IncludeMetadata: true,
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorQuery;
use Upstash\Vector\SparseVector;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->query(new VectorQuery(
  vector: [0.5, 0.4],
  sparseVector: new SparseVector(
    indices: [3, 5],
    values: [0.3, 0.5],
  ),
  topK: 5,
  includeMetadata: true,
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/query \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"vector": [0.5, 0.4], "sparseVector": {"indices": [3, 5], "values": [0.3, 0.5]}, "topK": 5, "includeMetadata": true}'
```

</Tab>

</Tabs>

The query results will be fused scores from the dense and sparse indexes.

#### Querying with Text Data

If you created the hybrid index with Upstash-hosted dense and sparse embedding models,
you can query with text data, and Upstash can embed it behind the scenes
before performing the actual query.

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.query(
    data="Upstash Vector",
    top_k=5,
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.query(
  {
    data: "Upstash Vector",
    topK: 1,
  },
)
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex(
		"UPSTASH_VECTOR_REST_URL",
		"UPSTASH_VECTOR_REST_TOKEN",
	)

	scores, err := index.QueryData(vector.QueryData{
		Data: "Upstash Vector",
		TopK: 5,
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\DataQuery;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->queryData(new DataQuery(
  data: 'Upstash Vector',
  topK: 5,
  includeMetadata: true,
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/query-data \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"data": "Upstash Vector", "topK": 5}'
```

</Tab>

</Tabs>

### Fusing Dense And Sparse Query Scores

One of the most crucial parts of the hybrid search pipeline is the step
where we fuse or rerank dense and sparse search results.

By default, Upstash returns the hybrid query results by fusing/reranking
the dense and the sparse search results. It provides two fusing algorithms
to choose from to do so.

#### Reciprocal Rank Fusion

RRF is a method for combining results from dense and sparse indexes.
It focuses on the order of results, not their scores. Each result's score
is mapped using the formula:

```
Mapped Score = 1 / (rank + K)
```

Here, rank is the position of the result in the dense or sparse scores, and `K`
is a constant set to `60`.

If a result appears in both the dense and sparse indexes, its mapped scores are
added together. If it appears in only one of the indexes, its score remains unchanged.
After all scores are processed, the results are sorted by their combined scores,
and the top-K results are returned.

RRF effectively combines rankings from different sources, making use of their strengths,
while keeping the process simple and focusing on the order of results.

By default, hybrid indexes use RRF to fuse dense and sparse scores. It can be explicitly
set for queries as follows:

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index
from upstash_vector.types import FusionAlgorithm, SparseVector

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.query(
    vector=[0.5, 0.4],
    sparse_vector=SparseVector([3, 5], [0.3, 0.5]),
    fusion_algorithm=FusionAlgorithm.RRF,
)
```

</Tab>

<Tab title="JavaScript">

```js
import { FusionAlgorithm, Index } from "@upstash/vector";

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
});

await index.query({
  vector: [0.5, 0.4],
  sparseVector: {
    indices: [2, 3],
    values: [0.13, 0.87],
  },
  fusionAlgorithm: FusionAlgorithm.RRF,
  topK: 3,
});
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex(
		"UPSTASH_VECTOR_REST_URL",
		"UPSTASH_VECTOR_REST_TOKEN",
	)

	scores, err := index.Query(vector.Query{
		Vector: []float32{0.5, 0.4},
		SparseVector: &vector.SparseVector{
			Indices: []int32{3, 5},
			Values:  []float32{0.3, 05},
		},
		FusionAlgorithm: vector.FusionAlgorithmRRF,
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorQuery;
use Upstash\Vector\SparseVector;
use Upstash\Vector\Enums\FusionAlgorithm;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->query(new VectorQuery(
  vector: [0.5, 0.4],
  sparseVector: new SparseVector(
    indices: [3, 5],
    values: [0.3, 0.5],
  ),
  topK: 5,
  includeMetadata: true,
  fusionAlgorithm: FusionAlgorithm::RECIPROCAL_RANK_FUSION,
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/query \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"vector": [0.5, 0.4], "sparseVector": {"indices": [3, 5], "values": [0.3, 0.5]}, "fusionAlgorithm": "RRF"}'
```

</Tab>

</Tabs>

#### Distribution-Based Score Fusion

DBSF is a method for combining results from dense and sparse indexes by considering
the distribution of scores. Each score is normalized using the formula:

```
                        s − (μ − 3 * σ)
Normalized Score = -------------------------
                   (μ + 3 * σ) − (μ − 3 * σ)
```

Where:

* `s` is the score.
* `μ` is the mean of the scores.
* `σ` is the standard deviation.
* `(μ − 3 * σ)` represents the minimum value (lower tail of the distribution).
* `(μ + 3 * σ)` represents the maximum value (upper tail of the distribution).

This formula scales each score to fit between 0 and 1 based on the range defined by
the distribution's tails.

If a result appears in both the dense and sparse indexes, the normalized scores
are added together. For results that appear in only one index, the individual
normalized score is used. After all scores are processed, the results are
sorted by their combined scores, and the top-K results are returned.

Unlike RRF, this approach takes the distribution of scores into account,
making it more sensitive to variations in score ranges from the dense and sparse indexes.

It can be used in hybrid index queries as follows:

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index
from upstash_vector.types import FusionAlgorithm, SparseVector

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.query(
    vector=[0.5, 0.4],
    sparse_vector=SparseVector([3, 5], [0.3, 0.5]),
    fusion_algorithm=FusionAlgorithm.DBSF,
)
```

</Tab>

<Tab title="JavaScript">

```js
import { FusionAlgorithm, Index } from "@upstash/vector";

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
});

await index.query({
  vector: [0.5, 0.4],
  sparseVector: {
    indices: [2, 3],
    values: [0.13, 0.87],
  },
  fusionAlgorithm: FusionAlgorithm.DBSF,
  topK: 3,
});
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex(
		"UPSTASH_VECTOR_REST_URL",
		"UPSTASH_VECTOR_REST_TOKEN",
	)

	scores, err := index.Query(vector.Query{
		Vector: []float32{0.5, 0.4},
		SparseVector: &vector.SparseVector{
			Indices: []int32{3, 5},
			Values:  []float32{0.3, 05},
		},
		FusionAlgorithm: vector.FusionAlgorithmDBSF,
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorQuery;
use Upstash\Vector\SparseVector;
use Upstash\Vector\Enums\FusionAlgorithm;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->query(new VectorQuery(
  vector: [0.5, 0.4],
  sparseVector: new SparseVector(
    indices: [3, 5],
    values: [0.3, 0.5],
  ),
  topK: 5,
  includeMetadata: true,
  fusionAlgorithm: FusionAlgorithm::DISTRIBUTION_BASED_SCORE_FUSION,
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/query \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"vector": [0.5, 0.4], "sparseVector": {"indices": [3, 5], "values": [0.3, 0.5]}, "fusionAlgorithm": "DBSF"}'
```

</Tab>

</Tabs>

#### Using a Custom Reranker

For some use cases, you might need something other than RRF or DBSF.
Maybe you want to use the [bge-reranker-v2-m3](https://huggingface.co/BAAI/bge-reranker-v2-m3),
or any reranker model or algorithm of your choice on the dense and sparse
components of the hybrid index.

For such scenarios, hybrid indexes allow you to perform queries over
only dense and only sparse components. This way, the hybrid index
would return semantically similar vectors from the dense index, and
exact query matches from the sparse index. Then, you can rerank them
as you like.

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index
from upstash_vector.types import SparseVector

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

dense_results = index.query(
    vector=[0.5, 0.4],
)

sparse_results = index.query(
    sparse_vector=SparseVector([3, 5], [0.3, 0.5]),
)

# Rerank dense and sparse results as you like here
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

const denseResults = await index.query(
  {
    vector: [0.5, 0.4],
    topK: 3,
  },
)

const sparseResults = await index.query(
  {
    sparseVector: {
      indices: [2, 3],
      values: [0.13, 0.87],
    },
    topK: 3,
  },
)

// Rerank dense and sparse results as you like here
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex(
		"UPSTASH_VECTOR_REST_URL",
		"UPSTASH_VECTOR_REST_TOKEN",
	)

	denseScores, err := index.Query(vector.Query{
		Vector: []float32{0.5, 0.4},
	})

	sparseScores, err := index.Query(vector.Query{
		SparseVector: &vector.SparseVector{
			Indices: []int32{3, 5},
			Values:  []float32{0.3, 05},
		},
	})

	// Rerank dense and sparse results as you like here
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorQuery;
use Upstash\Vector\SparseVector;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$denseResults = $index->query(new VectorQuery(
  vector: [0.5, 0.4],
  topK: 3,
));

$sparseResults = $index->query(new VectorQuery(
  sparseVector: new SparseVector(
    indices: [3, 5],
    values: [0.3, 0.5],
  ),
  topK: 3,
));

// Rerank dense and sparse results as you like here
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/query \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"vector": [0.5, 0.4]}'

curl $UPSTASH_VECTOR_REST_URL/query \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"sparseVector": {"indices": [3, 5], "values": [0.3, 0.5]}}'
```

</Tab>

</Tabs>

#### Using a Custom Reranker with Text Data

Similar the section above, you might want to use a custom reranker
for the hybrid indexes created with Upstash-hosted embedding models.

For such scenarios, hybrid indexes with Upstash-hosted embedding models
allow you to perform queries over only dense and only sparse components.
This way, the hybrid index would return semantically similar vectors
from the dense index by embedding the text data into a dense vector,
and exact query matches from the sparse index by embedding the text data
into a sparse vector. Then, you can rerank them as you like.

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index
from upstash_vector.types import SparseVector, QueryMode

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

dense_results = index.query(
    data="Upstash Vector",
    query_mode=QueryMode.DENSE,
)

sparse_results = index.query(
    data="Upstash Vector",
    query_mode=QueryMode.SPARSE,
)

# Rerank dense and sparse results as you like here
```

</Tab>

<Tab title="JavaScript">

```js
import { Index, QueryMode } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

const denseResults = await index.query({
  data: "Upstash Vector",
  queryMode: QueryMode.DENSE,
})

const sparseResults = await index.query({
  data: "Upstash Vector",
  queryMode: QueryMode.SPARSE,
})

// Rerank dense and sparse results as you like here
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex(
		"UPSTASH_VECTOR_REST_URL",
		"UPSTASH_VECTOR_REST_TOKEN",
	)

	denseScores, err := index.QueryData(vector.QueryData{
		Data:      "Upstash Vector",
		QueryMode: vector.QueryModeDense,
	})

	sparseScores, err := index.QueryData(vector.QueryData{
		Data:      "Upstash Vector",
		QueryMode: vector.QueryModeSparse,
	})

	// Rerank dense and sparse results as you like here
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\DataQuery;
use Upstash\Vector\Enums\QueryMode;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$denseResults = $index->queryData(new DataQuery(
  data: 'Upstash Vector',
  topK: 3,
  queryMode: QueryMode::DENSE,
));

$sparseResults = $index->queryData(new DataQuery(
  data: 'Upstash Vector',
  topK: 3,
  queryMode: QueryMode::SPARSE,
));

// Rerank dense and sparse results as you like here
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/query-data \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"data": "Upstash Vector", "queryMode": "DENSE"}'

curl $UPSTASH_VECTOR_REST_URL/query-data \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"data": "Upstash Vector", "queryMode": "SPARSE"}'
```

</Tab>

</Tabs>

# Metadata and Data
Source: https://upstash.com/docs/vector/features/metadata

## Metadata

Metadata feature allows you to store context with your vectors to make a connection.
There can be a couple of uses of this:

1. You can put the source of the vector in the metadata to use in your application from the query response.
2. You can put some metadata to further filter the results upon the query.

You can set metadata with your vector as follows:

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.upsert(
    [("id-0", [0.9215, 0.3897]), {"url": "https://imgur.com/z9AVZLb"}],
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.upsert({
  "id": "id-0",
  vector: [0.9215, 0.3897],
  metadata: {
    url: "https://imgur.com/z9AVZLb",
  },
})
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex("UPSTASH_VECTOR_REST_URL", "UPSTASH_VECTOR_REST_TOKEN")

	index.Upsert(vector.Upsert{
		Id:       "id-0",
		Vector:   []float32{0.9215, 0.3897},
		Metadata: map[string]any{"url": "https://imgur.com/z9AVZLb"},
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorUpsert;

use function Upstash\Vector\createRandomVector;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->upsert(new VectorUpsert(
  id: 'id-0',
  vector: createRandomVector(384),
  metadata: [
    'url' => "https://imgur.com/z9AVZLb",
  ],
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/upsert \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{
   "id":"id-0",
   "vector":[0.9215,0.3897],
   "metadata":{
      "url":"https://imgur.com/z9AVZLb"
   }
}'
```

</Tab>

</Tabs>

When you do a query or fetch, you can opt-in to retrieve the metadata as follows:

* **Query Example**

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.query(
    [0.9215, 0.3897],
    top_k=5,
    include_metadata=True,
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.query({
  vector: [0.9215, 0.3897],
  topK: 5,
  includeMetadata: true,
})
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex("UPSTASH_VECTOR_REST_URL", "UPSTASH_VECTOR_REST_TOKEN")

	index.Query(vector.Query{
		Vector:          []float32{0.9215, 0.3897},
		TopK:            5,
		IncludeMetadata: true,
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorQuery;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->query(new VectorQuery(
  vector: [0.9215, 0.3897],
  topK: 5,
  includeMetadata: true,
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/query \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{
   "vector":[0.9215,0.3897],
   "topK" : 5,
   "includeMetadata": true
}'
```

</Tab>

</Tabs>

```json
{
  "result": [
    {
      "id": "id-0",
      "score": 1,
      "metadata": {
        "url": "https://imgur.com/z9AVZLb"
      }
    },
    {
      "id": "id-3",
      "score": 0.99961007,
      "metadata": {
        "url": "https://imgur.com/zfOPmnI"
      }
    }
  ]
}
```

Also, you can filter the results further by providing a metadata filter:

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.query(
    [0.9215, 0.3897],
    top_k=5,
    include_metadata=True,
    filter="url GLOB '*imgur.com*'",
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.query({
  vector: [0.9215, 0.3897],
  topK: 5,
  includeMetadata: true,
  filter: "url GLOB '*imgur.com*'",
})
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex("UPSTASH_VECTOR_REST_URL", "UPSTASH_VECTOR_REST_TOKEN")

	index.Query(vector.Query{
		Vector:          []float32{0.9215, 0.3897},
		TopK:            5,
		IncludeMetadata: true,
		Filter:          "url GLOB '*imgur.com*'",
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorQuery;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->query(new VectorQuery(
  vector: [0.9215, 0.3897],
  topK: 5,
  includeMetadata: true,
  filter: "url GLOB '*imgur.com*'",
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/query \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{
   "vector":[0.9215,0.3897],
   "topK" : 5,
   "includeMetadata": true,
   "filter": "url GLOB \"*imgur.com*\""
}'
```
</Tab>

</Tabs>

See [Metadata Filtering documentation](/docs/vector/features/filtering) for more details.

* **Range Example**

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.range(
    cursor="0",
    limit=3,
    include_metadata=True,
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.range({
  cursor: "0",
  limit: 3,
  includeMetadata: true,
})
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex("UPSTASH_VECTOR_REST_URL", "UPSTASH_VECTOR_REST_TOKEN")

	index.Range(vector.Range{
		Cursor:          "0",
		Limit:           3,
		IncludeMetadata: true,
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorRange;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->range(new VectorRange(
  limit: 3,
  includeMetadata: true,
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/range \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{ "cursor" : "0",  "limit" : 3, "includeMetadata": true}'
```
</Tab>

</Tabs>

```json
{
  "result": {
    "nextCursor": "4",
    "vectors": [
      { "id": "id-0", "metadata": { "url": "https://imgur.com/z9AVZLb" } },
      { "id": "id-1", "metadata": { "url": "https://imgur.com/a2nCEIt" } },
      { "id": "id-2", "metadata": { "url": "https://imgur.com/zfOPmnI" } }
    ]
  }
}
```

## Data

Data is another kind of information you can store per vector
to attribute some context to it. Compared to metadata, it is not
structured, and it can only be fetched in queries, not used
to further filter them.

It is especially useful when you upsert raw text data, so that you
would have access to the textual form of vector along with the
embedded vector values.

It can save you from storing contextual information per vector
in a separate database.

You can set both the metadata and data, or only one of them
while upserting your vectors as follows:

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.upsert(
    [
        {
            "id": "id-0",
            "vector": [0.9215, 0.3897],
            "metadata": {"url": "https://imgur.com/z9AVZLb"},
            "data": "data-0",
        },
        {
            "id": "id-1",
            "vector": [0.3897, 0.9215],
            "data": "data-1",
        },
    ],
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.upsert([
  {
    id: "id-0",
    vector: [0.9215, 0.3897],
    metadata: {"url": "https://imgur.com/z9AVZLb"},
    data: "data-0",
  },
  {
    id: "id-1",
    vector: [0.3897, 0.9215],
    data: "data-1",
  },
])
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorUpsert;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->upsertMany([
  new VectorUpsert(
    id: 'id-0',
    vector: [0.9215, 0.3897],
    data: 'data-0',
  ),
  new VectorUpsert(
    id: 'id-1',
    vector: [0.3897, 0.9215],
    data: 'data-1',
  ),
]);
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/upsert \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '[
        {
            "id": "id-0",
            "vector": [0.9215, 0.3897],
            "metadata": {"url": "https://imgur.com/z9AVZLb"},
            "data": "data-0"
        },
        {
            "id": "id-1",
            "vector": [0.3897, 0.9215],
            "data": "data-1"
        }
    ]'
```

</Tab>

</Tabs>

When a raw text data is upserted, the data will be set to
the raw text data automatically:

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.upsert(
    [
        {
            "id": "id-2",
            "data": "Upstash is a serverless data platform.",
        },
    ],
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.upsert([
  {
    id: "id-2",
    data: "Upstash is a serverless data platform.",
  }
])
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\DataUpsert;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->upsertData(new DataUpsert(
  id: 'id-0',
  data: 'Upstash is a serverless data platform.',
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/upsert-data \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{
        "id": "id-0",
        "data": "Upstash is a serverless data platform."
      }'
```

</Tab>

</Tabs>

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

result = index.query(
    data="What is Upstash?",
    include_data=True,
)

for res in result:
    print(f"{res.id}: {res.data}")
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

const result = await index.query({
  data: "What is Upstash?",
  includeData: true,
  topK: 3
})

for (const vector of result) {
  console.log(`${vector.id}: ${vector.data}`)
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\DataQuery;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$results = $index->queryData(new DataQuery(
  data: 'Upstash is a serverless data platform.',
  topK: 3
  includeData: true,
));

foreach ($results as $result) {
  print_r($result->toArray());
}
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/query-data \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{
        "data": "What is Upstash?",
        "includeData": true,
      }'
```

</Tab>

</Tabs>

Similar to metadata, the data field can be requested in queries, range
iterator, and fetch requests, by setting the `includeData` to `true` as
shown above.

# Namespaces
Source: https://upstash.com/docs/vector/features/namespaces

Upstash Vector allows you to partition a single index into multiple isolated namespaces. Each namespace acts as a self-contained subset of the index, and read and write requests are limited to one namespace.

Each vector index has at least one default namespace and optionally many more.

If no namespace is specified, the operations will use the default namespace, which has the name `""` (empty string).

<Note>
  Indexes created before the namespaces feature can still be used as they are,
  without modification. All operations are assumed to use the default namespace.
</Note>

## Using a Namespace

Namespaces are created implicitly when an upsert operation is performed, so there is no specific endpoint for creating a namespace.

For example, the code snippet below will create the namespace `ns` if it does not already exist, upsert and query the vector only on that namespace.

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.upsert(
    [("id-0", [0.9215, 0.3897])],
    namespace="ns",
)

index.query(
    [0.9215, 0.3897],
    top_k=5,
    namespace="ns",
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

const namespace = index.namespace("ns")

await namespace.upsert({
  id: "id-0",
  vector: [0.9215, 0.3897],
})

await namespace.query({
  vector: [0.9215, 0.3897],
  topK: 5,
})
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex("UPSTASH_VECTOR_REST_URL", "UPSTASH_VECTOR_REST_TOKEN")

	namespace := index.Namespace("ns")

	namespace.Upsert(vector.Upsert{
		Id:     "id-0",
		Vector: []float32{0.9215, 0.3897},
	})

	namespace.Query(vector.Query{
		Vector: []float32{0.9215, 0.3897},
		TopK:   5,
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorUpsert;
use Upstash\Vector\VectorQuery;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$namespace = $index->namespace('ns');

$namespace->upsert(new VectorUpsert(
  id: 'id-0',
  vector: [0.9215, 0.3897],
));

$namespace->query(new VectorQuery(
  vector: [0.9215, 0.3897],
  topK: 5,
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/upsert/ns \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"id":"id-0", "vector":[0.9215,0.3897]}'

curl $UPSTASH_VECTOR_REST_URL/query/ns \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"vector":[0.9215,0.3897], "topK" : 5}'
```

</Tab>

</Tabs>

Under the hood, when using a namespace, your requests are sent to `<vector-url>/<command>/namespace-name` to only be executed only against that namespace.

## Deleting a Namespace

Namespaces can be deleted by using the
[Delete Namespace](../api/endpoints/delete-namespace) API.

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.delete_namespace("ns")
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.deleteNamespace("ns")
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex("UPSTASH_VECTOR_REST_URL", "UPSTASH_VECTOR_REST_TOKEN")

	namespace := index.Namespace("ns")

	namespace.DeleteNamespace()
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->namespace('ns')->deleteNamespace();
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/delete-namespace/ns \
  -X DELETE \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN"
```

</Tab>

</Tabs>

## Listing Namespaces

All active namespaces can be listed by using the
[List Namespaces](../api/endpoints/list-namespaces) API.

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.list_namespaces()
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.listNamespaces()
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex("UPSTASH_VECTOR_REST_URL", "UPSTASH_VECTOR_REST_TOKEN")

	index.ListNamespaces()
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->listNamespaces();
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/list-namespaces \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN"
```

</Tab>

</Tabs>

# Resumable Query
Source: https://upstash.com/docs/vector/features/resumablequery

When searching for approximate nearest neighbors of a vector with regular query API,
you specify a topK so that the vector index returns at most that many results.

In regular query API, it is not possible to continue the query where it is left off, as
each query runs from start to completion, before returning a response.

However, there might be cases where you want to get the next topK many similar vectors
after running a regular query. Perhaps, you wanted to fetch the next batch of vectors
for the next page of your search functionality.

Resumable query helps you to achieve that. It has the same features as the regular query;
you can specify a filter, get values, metadata or data of the vectors, or specify a topK
value. But, after getting the initial response from the server, it allows you to query
for more, starting from where it left off instead of the start.

## Starting a Resumable Query

Each resumable query starts in this phase. Using our SDKs or the REST API, you can
initiate a resumable query. The filter or flags to include vector values, metadata,
or data will be valid for the entire duration of the resumable query.

When you start the query, the server responds with the first batch of the most
similar vectors and a handle that uniquely identifies the query so that you
can continue the query where you left off.

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

result, handle = index.resumable_query(
    vector=[0.1, 0.2],
    top_k=2,
    include_metadata=True,
)

# first batch of the results
for r in result:
    print(r)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
});

const { result, fetchNext, stop } = await index.resumableQuery({
  vector: [0.1, 0.2],
  topK: 2,
  includeMetadata: true,
});

// first batch of the results
for (let r of result) {
  console.log(r);
}
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"fmt"
	"log"

	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex(
		"UPSTASH_VECTOR_REST_URL",
		"UPSTASH_VECTOR_REST_TOKEN",
	)

	scores, handle, err := index.ResumableQuery(vector.ResumableQuery{
		Vector:          []float32{0.1, 0.2},
		TopK:            2,
		IncludeMetadata: true,
	})
	if err != nil {
		log.Fatal(err)
	}

	defer handle.Close()

	// first batch of the results
	for _, score := range scores {
		fmt.Printf("%+v\n", score)
	}
}
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/resumable-query \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{
    "vector": [0.1, 0.2],
    "topK": 2,
    "includeMetadata": true
  }'
```

</Tab>

</Tabs>

We support resumable queries with raw vector values as shown above,
or with raw text data for indexes created with Upstash hosted embedding models.

## Resuming the Resumable Query

Using the handle returned by the initial call to resumable query,
you can get the next batch of the most similar vectors, starting
from the last response.

You can fetch as many next batch as possible until you iterate
over the entire index.

<Tabs>

<Tab title="Python">

```python
# next batch of the results
next_result = handle.fetch_next(
    additional_k=3,
)

for r in next_result:
    print(r)

# it is possible to call fetch_next more than once
next_result = handle.fetch_next(
    additional_k=5,
)

for r in next_result:
    print(r)
```

</Tab>

<Tab title="JavaScript">

```js
// next batch of the results
let nextResult = await fetchNext(3);

for (let r of nextResult) {
  console.log(r);
}

// it is possible to call fetch_next more than once
nextResult = await fetchNext(3);

for (let r of nextResult) {
  console.log(r);
}
```

</Tab>

<Tab title="Go">

```go
// next batch of the results
scores, err = handle.Next(vector.ResumableQueryNext{
	AdditionalK: 3,
})
if err != nil {
	log.Fatal(err)
}

for _, score := range scores {
	fmt.Printf("%+v\n", score)
}

// it is possible to call Next more than once
scores, err = handle.Next(vector.ResumableQueryNext{
	AdditionalK: 5,
})
if err != nil {
	log.Fatal(err)
}

for _, score := range scores {
	fmt.Printf("%+v\n", score)
}
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/resumable-query-next \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{
    "uuid": "550e8400-e29b-41d4-a716-446655440000",
    "additionalK": 3
  }'
```

</Tab>

</Tabs>

## Stopping the Resumable Query

Each resumable query requires us to maintain a state in the server.
So, there is a limit for the maximum number of active resumable queries
at a time.

That's why it is important to stop the resumable query once you are
done with it.

Resumable queries can be stopped using the handle returned with the
initial call.

<Tabs>

<Tab title="Python">

```python
handle.stop()
```

</Tab>

<Tab title="JavaScript">

```js
await stop();
```

</Tab>

<Tab title="Go">

```go
handle.Close()
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/resumable-query-end \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{
    "uuid": "550e8400-e29b-41d4-a716-446655440000"
  }'
```

</Tab>

</Tabs>

We periodically scan resumable queries to stop the idle ones, so
queries that have not touched for some time are stopped automatically.

By default, the max idle time of a resumable query is 1 hour.
You can configure this behavior by specifying a max idle time in seconds
while starting the resumable query.

<Tabs>

<Tab title="Python">

```python
result, handle = index.resumable_query(
    vector=[0.1, 0.2],
    top_k=2,
    include_metadata=True,
    max_idle = 7200, # two hours, in seconds
)
```

</Tab>

<Tab title="JavaScript">

```js
const { result, fetchNext, stop } = await index.resumableQuery({
  vector: [0.1, 0.2],
  topK: 2,
  includeMetadata: true,
  maxIdle: 7200, // two hours, in seconds
});
```

</Tab>

<Tab title="Go">

```go
scores, handle, err := index.ResumableQuery(vector.ResumableQuery{
	Vector:          []float32{0.1, 0.2},
	TopK:            2,
	IncludeMetadata: true,
	MaxIdle:         7200, // two hours, in seconds
})
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/resumable-query \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{
    "vector": [0.1, 0.2],
    "topK": 2,
    "includeMetadata": true,
    "maxIdle": 7200
  }'
```

</Tab>

</Tabs>

# Vector Similarity Functions
Source: https://upstash.com/docs/vector/features/similarityfunctions

When creating a vector index in Upstash Vector, you have the flexibility to choose from different vector similarity functions.
Each function yields distinct query results, catering to specific use cases. Here are the three supported similarity functions:

<Note>
  The score returned from query requests is a normalized value between 0 and 1,
  where 1 indicates the highest similarity and 0 the lowest regardless of the
  similarity function used.
</Note>

#### Cosine Similarity

Cosine similarity measures the cosine of the angle between two vectors. It is particularly useful when the magnitude of the vectors is not essential, and the focus is on the orientation.

**Use Cases:**

* **Natural Language Processing (NLP):** Ideal for comparing document embeddings or word vectors, as it captures semantic similarity irrespective of vector magnitude.
* **Recommendation Systems:** Effective in recommending items based on user preferences or content similarities.

**Score calculation:**
` (1 + cosine_similarity(v1, v2)) / 2;`

  <img
    className="block h-32 dark:hidden"
    src="/img/vector/cosine_similarity.svg"
  />
  <img
    className="hidden h-32 dark:block"
    src="/img/vector/cosine_similarity_dark.svg"
  />

#### Euclidean Distance

Euclidean distance calculates the straight-line distance between two vectors in a multi-dimensional space. It is well-suited for scenarios where the magnitude of vectors is crucial, providing a measure of their spatial separation.

**Use Cases:**

* **Computer Vision:** Useful in image processing tasks, such as image recognition or object detection, where the spatial arrangement of features is significant.
* **Anomaly Detection:** Valuable for detecting anomalies in datasets, as it considers both the direction and magnitude of differences between vectors.

**Score calculation:**
`1 / (1 + squared_distance(v1, v2))`

  <img
    className="block h-32 dark:hidden"
    src="/img/vector/squared_distance.svg"
  />
  <img
    className="hidden h-32 dark:block"
    src="/img/vector/squared_distance_dark.svg"
  />

#### Dot Product

The dot product measures the similarity by multiplying the corresponding components of two vectors and summing the results. It provides a measure of alignment between vectors.
Note that to use dot product, the vectors needs to be normalized to be of unit length.

**Use Cases:**

* **Machine Learning Models:** Commonly used in machine learning for tasks like sentiment analysis or classification, where feature alignment is critical.
* **Collaborative Filtering:** Effective in collaborative filtering scenarios, such as recommending items based on user behavior or preferences.

**Score calculation:**
` (1 + dot_product(v1, v2)) / 2`

  <img className="block h-32 dark:hidden" />
  <img
    className="hidden h-32 dark:block"
    src="/img/vector/dot_product_dark.svg"
  />

# Sparse Indexes
Source: https://upstash.com/docs/vector/features/sparseindexes

Sparse vectors are representations in a high-dimensional space,
where only a small number of dimensions have non-zero values.

For example, for the same text, a dense vector representation with
the BGE-M3 model would have 1024 non-zero valued dimensions.
However, the sparse vector representation of the same text would
have less than a hundred non-zero valued dimensions, whereas vector
space potentially has more than 250 thousand dimensions. Also, unlike
dense vector representations, sparse vectors might have varying
non-zero valued dimensions depending on the text.

Generally, sparse vectors can be represented with two arrays of equal
sizes:

* The first array for the indices contains the indices of the non-zero
  dimensions.
* The second array for values contains the floating point values for
  the non-zero dimensions.

```python
dense = [0.1, 0.3, , ...thousands of non-zero values..., 0.5, 0.2]

sparse = (
    [23, 42, 5523, 123987, 240001], # some low number of dimension indices
    [0.1, 0.3, 0.1, 0.2, 0.5], # non-zero values corresponding to dimensions
)
```

Unlike dense vectors which excel at approximate semantic matching,
sparse vectors are particularly useful for tasks that require exact or
near exact matching of tokens/words/features. That makes it useful
for various tasks, such as:

* **Information Retrieval and Text Analysis**: By representing documents
  as sparse vectors where each token/word would correspond to a dimension
  in high dimensional vocabulary; and varying values by the frequencies
  of the tokens/words in the document or by weighting them with inverse
  document frequencies to favor rare terms, you can build complex
  search pipelines.
* **Recommender Systems**: By representing user interactions, preferences,
  ratings, or purchases as sparse vectors, you can identify relevant
  recommendations, and personalize content delivery.

## Creating Sparse Vectors

There are various ways to create sparse vectors. You can use
[BM25](https://en.wikipedia.org/wiki/Okapi_BM25) for information
retrieval tasks, or use models like [SPLADE](https://github.com/naver/splade)
that enhance documents and queries with term weighting and expansion.

Upstash gives you full control by allowing you to upsert and query
sparse vectors.

Also, to make embedding easier for you, Upstash provides some hosted
models and allows you to upsert and query text data. Behind the scenes,
the text data is converted to sparse vectors.

You can create your index with a sparse embedding model to use this feature.

### BGE-M3 Sparse Vectors

BGE-M3 is a multi-functional, multi-lingual, and multi-granular model
widely used for dense indexes.

We also provide BGE-M3 as a sparse vector embedder, which outputs
sparse vectors from `250_002` dimensional space.

These sparse vectors have values where each token is weighted
according to the input text, which enhances traditional sparse vectors
with contextuality.

### BM25 Sparse Vectors

BM25 is a popular algorithm used in full-text search systems to rank
documents based on their relevance to a query.

This algorithm relies on key principles of term frequency,
inverse document frequency, and document length normalization,
making it well-suited for text retrieval tasks.

* **Rare terms are important**: BM25 gives more weight to words that are
  less common in the collection of documents. For example, in a search
  for “Upstash Vector”, the word “Upstash” might be considered more
  important than “Vector” if it appears less frequently across all documents.
* **Repeating a Word Helps—But Only Up to a Point**: BM25 considers how
  often a word appears in a document, but it limits the benefit of repeating
  the word too many times. This means mentioning “Upstash” a hundred times
  won’t make a document overly important compared to one that mentions
  it just a few times.
* **Shorter Documents Often Rank Higher**: Shorter documents that match
  the query are usually more relevant. BM25 adjusts for document length
  so longer documents don’t get unfairly ranked just because they contain
  more words.

Upstash provides a general purpose BM25 algorithm, that applies to documents
and queries in English. It tokenizes the text into words, removes stop words,
stems the remaining words, and assigns a weighted value to them, based on the
BM25 formula:

```
                       IDF(qᵢ) * f(qᵢ, D) * (k₁ + 1)
BM25(D, Q) = Σ ----------------------------------------------
               f(qᵢ, D) + k₁ * (1 - b + b * (|D| / avg(|D|)))
```

Where:

* `f(qᵢ, D)` is the frequency of term `qᵢ` in document `D`.
* `|D|` is the length of document `D`.
* `avg(|D|)` is the average document length in the collection.
* `k₁` is the term frequency saturation parameter.
* `b` is the length normalization parameter.
* `IDF(qᵢ)` is the inverse document frequency of term `qᵢ`

To make it a general purpose model, we had to decide on some of the
constants mentioned above, which would differ from implementation
to implementation. We decided to use the following values for

* `k₁` = `1.2`, a widely used value in the absence of advanced optimizations
* `b` = `0.75`, a widely used value in the absence of advanced optimizations
* `avg(|D|)` = `32`, which was chosen by tokenizing and taking the average of
  [MSMARCO](https://microsoft.github.io/msmarco/) dataset vectors, rounded
  to the nearest power of two.

In the future, we might provide support for more languages and the ability to
provide different values for the above constants.

As for the inverse document frequency `IDF(qᵢ)`, we maintain that information
per token in the vector database itself. You can use it by providing it
as the weighting strategy for your queries so that you don't have to weight
it yourself.

## Using Sparse Indexes

### Upserting Sparse Vectors

You can upsert sparse vectors into Upstash Vector indexes in two different ways.

#### Upserting Sparse Vectors

You can upsert sparse vectors by representing them as two arrays of equal
sizes. One signed 32-bit integer array for non-zero dimension indices,
and one 32-bit float array for the values.

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index, Vector
from upstash_vector.types import SparseVector

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.upsert(
    vectors=[
        Vector(id="id-0", sparse_vector=SparseVector([1, 2], [0.1, 0.2])),
        Vector(id="id-1", sparse_vector=SparseVector([123, 44232], [0.5, 0.4])),
    ]
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.upsert([{
  id: 'id-0',
  sparseVector: {
    indices: [2, 3],
    values: [0.13, 0.87],
  },
}])
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex(
		"UPSTASH_VECTOR_REST_URL",
		"UPSTASH_VECTOR_REST_TOKEN",
	)

	err := index.UpsertMany([]vector.Upsert{
		{
			Id: "id-0",
			SparseVector: &vector.SparseVector{
				Indices: []int32{1, 2},
				Values:  []float32{0.1, 0.2},
			},
		},
		{
			Id: "id-1",
			SparseVector: &vector.SparseVector{
				Indices: []int32{123, 44232},
				Values:  []float32{0.5, 0.4},
			},
		},
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorUpsert;
use Upstash\Vector\SparseVector;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->upsertMany([
  new VectorUpsert(
    id: 'id-0',
    sparseVector: new SparseVector(
      indices: [1, 2],
      values: [0.1, 0.2],
    ),
  ),
  new VectorUpsert(
    id: 'id-1',
    sparseVector: new SparseVector(
      indices: [123, 44232],
      values: [0.5, 0.4],
    ),
  ),
]);
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/upsert \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '[
    {"id": "id-0", "sparseVector": {"indices": [1, 2], "values": [0.1, 0.2]}},
    {"id": "id-1", "sparseVector": {"indices": [123, 44232], "values": [0.5, 0.4]}}
  ]'
```

</Tab>

</Tabs>

Note that, we do not allow sparse vectors to have more than `1_000` non-zero valued dimension.

#### Upserting Text Data

If you created the sparse index with an Upstash-hosted sparse embedding model,
you can upsert text data, and Upstash can embed it behind the scenes.

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index, Vector

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.upsert(
    vectors=[
        Vector(id="id-0", data="Upstash Vector provides sparse embedding models."),
        Vector(id="id-1", data="You can upsert text data with these embedding models."),
    ]
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.upsert([
  {
    id: 'id-0',
    data: "Upstash Vector provides dense and sparse embedding models.",
  }
])
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex(
		"UPSTASH_VECTOR_REST_URL",
		"UPSTASH_VECTOR_REST_TOKEN",
	)

	err := index.UpsertDataMany([]vector.UpsertData{
		{
			Id:   "id-0",
			Data: "Upstash Vector provides sparse embedding models.",
		},
		{
			Id:   "id-1",
			Data: "You can upsert text data with these embedding models.",
		},
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\DataUpsert;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->upsertDataMany([
  new DataUpsert(
    id: 'id-0',
    data: 'Upstash Vector provides sparse embedding models.',
  ),
  new DataUpsert(
    id: 'id-1',
    data: 'You can upsert text data with these embedding models.',
  ),
]);
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/upsert-data \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '[
    {"id": "id-0", "data": "Upstash Vector provides sparse embedding models."},
    {"id": "id-1", "data": "You can upsert text data with these embedding models."}
  ]'
```

</Tab>

</Tabs>

### Querying Sparse Vectors

Similar to upserts, you can query sparse vectors in two different ways.

#### Querying with Sparse Vectors

You can query sparse vectors by representing the sparse query vector
as two arrays of equal sizes. One signed 32-bit integer array for
non-zero dimension indices, and one 32-bit float array for the values.

We use the inner product similarity metric while calculating the
similarity scores, only considering the matching non-zero valued
dimension indices between the query vector and the indexed vectors.

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index
from upstash_vector.types import SparseVector

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.query(
    sparse_vector=SparseVector([3, 5], [0.3, 0.5]),
    top_k=5,
    include_metadata=True,
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.query(
  {
    sparseVector: {
      indices: [2, 3],
      values: [0.13, 0.87],
    },
    includeData: true,
    topK: 3,
  },
)
```
</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex(
		"UPSTASH_VECTOR_REST_URL",
		"UPSTASH_VECTOR_REST_TOKEN",
	)

	scores, err := index.Query(vector.Query{
		SparseVector: &vector.SparseVector{
			Indices: []int32{3, 5},
			Values:  []float32{0.3, 05},
		},
		TopK:            5,
		IncludeMetadata: true,
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorQuery;
use Upstash\Vector\SparseVector;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->query(new VectorQuery(
  sparseVector: new SparseVector(
    indices: [3, 5],
    values: [0.3, 0.5],
  ),
  topK: 5,
  includeMetadata: true,
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/query \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"sparseVector": {"indices": [3, 5], "values": [0.3, 0.5]}, "topK": 5, "includeMetadata": true}'
```

</Tab>

</Tabs>

Note that, the similarity scores are exact, not approximate. So, if there
are no vectors with one or more matching non-zero valued dimension indices with
the query vector, the result might be less than the provided top-K value.

#### Querying with Text Data

If you created the sparse index with an Upstash-hosted sparse embedding model,
you can query with text data, and Upstash can embed it behind the scenes
before performing the actual query.

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.query(
    data="Upstash Vector",
    top_k=5,
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index } from "@upstash/vector"

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.query(
  {
    data: "Upstash Vector",
    topK: 1,
  },
)
```

</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex(
		"UPSTASH_VECTOR_REST_URL",
		"UPSTASH_VECTOR_REST_TOKEN",
	)

	scores, err := index.QueryData(vector.QueryData{
		Data: "Upstash Vector",
		TopK: 5,
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\DataQuery;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->queryData(new DataQuery(
  data: 'Upstash Vector',
  topK: 5,
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/query-data \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"data": "Upstash Vector", "topK": 5}'
```

</Tab>

</Tabs>

#### Weighting Query Values

For algorithms like BM25, it is important to take the inverse
document frequencies that make matching rare terms more important
into account. It might be tricky to maintain that information
yourself, so Upstash Vector provides it out of the box. To make use
of IDF in your queries, you can pass it as a weighting strategy.

Since this is mainly meant to be used with BM25 models, the IDF
is defined as:

```
IDF(qᵢ) = log((N - n(qᵢ) + 0.5) / (n(qᵢ) + 0.5))
```

* `N` is the total number of documents in the collection.
* `n(qᵢ)` is the number of documents containing term `qᵢ`.

<Tabs>

<Tab title="Python">

```python
from upstash_vector import Index
from upstash_vector.types import WeightingStrategy

index = Index(
    url="UPSTASH_VECTOR_REST_URL",
    token="UPSTASH_VECTOR_REST_TOKEN",
)

index.query(
    data="Upstash Vector",
    top_k=5,
    weighting_strategy=WeightingStrategy.IDF,
)
```

</Tab>

<Tab title="JavaScript">

```js
import { Index, WeightingStrategy } from "@upstash/vector";

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
});

await index.query({
  sparseVector: {
    indices: [2, 3],
    values: [0.13, 0.87],
  },
  weightingStrategy: WeightingStrategy.IDF,
  topK: 3,
});
```
</Tab>

<Tab title="Go">

```go
package main

import (
	"github.com/upstash/vector-go"
)

func main() {
	index := vector.NewIndex(
		"UPSTASH_VECTOR_REST_URL",
		"UPSTASH_VECTOR_REST_TOKEN",
	)

	scores, err := index.QueryData(vector.QueryData{
		Data:              "Upstash Vector",
		TopK:              5,
		WeightingStrategy: vector.WeightingStrategyIDF,
	})
}
```

</Tab>

<Tab title="PHP">

```php
use Upstash\Vector\Index;
use Upstash\Vector\DataQuery;
use Upstash\Vector\Enums\WeightingStrategy;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->queryData(new DataQuery(
  data: 'Upstash Vector',
  topK: 5,
  weightingStrategy: WeightingStrategy::INVERSE_DOCUMENT_FREQUENCY,
));
```

</Tab>

<Tab title="curl">

```shell
curl $UPSTASH_VECTOR_REST_URL/query-data \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"data": "Upstash Vector", "topK": 5, "weightingStrategy": "IDF"}'
```

</Tab>

</Tabs>

# FAQ
Source: https://upstash.com/docs/vector/help/faq

**Account and Usage:**

* **What is the maximum number of indexes I can create in my account?**

    You can create up to 10 indexes per account. For additional needs, please send us an email to  support@upstash.com

* **What is the maximum dimension I can set for my indexes?**

    For the free tier, the maximum dimension can be 1536, for fixed and pay as you go tiers, this can be 3072, and for the pro tier, this can go up to 5376

* **How do you calculate the storage to be charged?**

    Storage is a sum of vector storage, metadata, and data storage. Vector storage depends on the number of dimensions and the number of vectors in that index. Each dimension is estimated to be 4 bytes, resulting in vector storage being calculated as vector count * dimension count * 4 bytes. For metadata and data storage, it is calculated based on the vector count in an index multiplied by the average metadata and data size, which can be up to 48 Kbytes and 1 Mbytes respectively.

* **What happens when I exceed my Metadata / Data Storage limit?**

    Metadata / Data Storage has a soft limit, and you will receive an email reminder to optimize your index for optimal performance if you approach this limit. The storage cost remains at $0.25 per GB. For enhanced performance, we recommend upgrading to Pro tier that meets your requirements, which includes incremental metadata and data storage

* **What happens when I exceed bandwidth limit of 200GB?**

    When you exceed the 200GB monthly bandwidth limit, you will be charged at $0.03 per GB for the additional usage. The initial 200GB is provided free of charge.

* **If I upload my dataset for pay as you go tier and don’t have any read/write operation during the month, how much will I pay?**

    You will only be charged for the storage of your index and metadata at a rate of $0.25 per GB. There are no hourly charges for having an index created on Upstash

* **How do you count Updates and Queries for billing purposes?**

    We charge $0.40 per 100,000 operations, which includes ranges, queries, fetches, upserts and deletes, all counted equally. Ranges, Queries and Fetches are all categorized as query type of operation, whereas Upserts and Deletes are counted as updates. If upsert and delete operations involve multiple vectors at a time, the billable request count will be equal to number of vectors upserted or deleted at one request.

* **If an update/query return or insert multiple vectors at a time, will each vector be counted as separate operations or will that be counted as one operation for billing purposes?**

   For the query type of operations, irrespective of the number of vectors involved in it, we count the operation as one and bill accordingly. For example, a fetch operation can return 10 vectors, and this will be counted only as 1 operation for billing purposes. However for update operations (upsert and delete), number of vectors involved in the operations is reflected as billable request count. For example a batch upsert of 1000 vectors is counted as 1000 billable request.

* **What is the maximum number of vectors I can have in an index?**

    The maximum number of vectors in an index depends on the dimension size. Every plan has a max vector*dimension limit. For pay as you go tier, this limit is set at 2 billion. For instance, under this plan a user can have an index with a size of 5 million vectors and a vector dimension of 400. For details about the limits of each plan, check our pricing page.

**Functionality:**

* **Do you support hybrid search?**

    We don’t support hybrid search at the moment. Although this is in our roadmap we still haven’t finalized the approach to help our users eliminate the need to have a secondary database for better search result. As we define our approach on implementing hybrid search and gather more user feedback, we will share this through our blog posts

* **Can I filter by metadata?**

    Yes, see [Metadata Filtering](../features/filtering).

* **Do you support replication?**

    Replication is not supported in the current release, but we are actively working on including it in our upcoming release.

* **If I do not specify a UUID during adding vectors, will Upstash Vector create one automatically?**

    No, ID field is required and cannot be an empty string while inserting vectors to your index

**Data Management:**

* **How can I upload a large dataset quickly?**

    You can perform batch inserts to upload datasets more efficiently. You don't need a separate batch specific operation to insert multiple vectors. The 'upsert' operation accepts either a single vector or an array of vectors. We recommend using an array size of up to 1000 for efficient batch inserts. For more details, please refer to the documentation

* **How can I remove a metadata field from a vector?**

    You need to run Upsert function on the same vector ID to remove existing metadata fields from a vector. Check our  documentation on using `Upsert`,

**Infrastructure and Availability:**

* **Which cloud provider is Upstash Vector hosted on?**

    Upstash is currently offered on AWS. We have plans to expand it to other cloud providers, please reach out support@upstash.com to raise this request as this will help us prioritize accordingly.

* **In what regions is Upstash available?**

    Upstash Vector is currently available in two regions; AWS us-east1 and AWS eu-west-1. We are in the process of expanding to more regions where other Upstash products are currently offered.

* **Should I be on AWS to be able to use Upstash Vector?**

    No. Your client can be anywhere but the clients in AWS regions will give you better performance.

**Feature Request and Upgrade:**

* **How can I request a feature during this beta release?**

    Please reach out to support@upstash.com. You can also raise your feature request through our chatbot, located at the bottom of Upstash page. The chatbot will guide you through the process of submitting your feature request.

* **How to upgrade from free tier to pay-as-you-go tier?**

    To upgrade from the free tier to the pay-as-you-go tier, simply enter your credit card information. This will automatically upgrade your account to the pay-as-you-go tier, allowing you to take advantage of the additional features and capabilities.

# Vercel AI SDK with Upstash Vector
Source: https://upstash.com/docs/vector/integrations/ai-sdk

The [AI SDK](https://sdk.vercel.ai/docs/introduction) is a TypeScript toolkit designed to help developers build AI-powered applications using React, Next.js, Vue, Svelte, Node.js, and more.

Upstash Vector integrates with the AI SDK to provide AI applications with the benefits of vector databases, enabling applications to perform semantic search and RAG (Retrieval-Augmented Generation).

In this guide, we’ll build a RAG chatbot using the AI SDK. This chatbot will be able to both store and retrieve information from a knowledge base. We’ll use Upstash Vector as our vector database, and the OpenAI API to generate responses.

## Prerequisites

Before getting started, make sure you have:

* An Upstash account (to upsert and query data)
* An OpenAI API key (to generate responses and embeddings)

## Setup and Installation

We will start by bootstrapping a Next.js application with the following command:

```bash
npx create-next-app rag-chatbot --typescript
cd rag-chatbot
```

Next, we will install the required packages using the following command:

<CodeGroup>
```bash npm
npm install @ai-sdk/openai ai zod @upstash/vector
```

```bash pnpm
pnpm install @ai-sdk/openai ai zod @upstash/vector
```

```bash bun
bun install @ai-sdk/openai ai zod @upstash/vector
```
</CodeGroup>

We need to set the following environment variables in our `.env` file:

```bash
OPENAI_API_KEY=your_openai_api_key
UPSTASH_VECTOR_REST_URL=your_upstash_url
UPSTASH_VECTOR_REST_TOKEN=your_upstash_token
```

You can get your Upstash credentials after creating a Vector Index in the [Upstash Console](https://console.upstash.com).

<Info>
If you are going to use Upstash hosted embedding models, you should select one of the available options when creating your index. If you are going to use custom embedding models, you should specify the dimensions of your embedding model.
</Info>

## Implementation

**RAG (Retrieval-Augmented Generation)** is the process of enabling the model to respond with information outside of its training data by embedding a user's query, retrieving the relevant source material (chunks) with the highest semantic similarity, and then passing them alongside the initial query as context.

Let's consider a simple example. Initially, a chatbot doesn't know who your favorite basketball player is. During a conversation, I inform the chatbot that my favorite player is Alperen Sengun, and it stores this information in its knowledge base. Later, in another conversation, when I ask, "Who is my favorite basketball player?" the chatbot retrieves this information from the knowledge base and responds with "Alperen Sengun."

### Chunking + Embedding Logic

**Embeddings** are a way to represent the semantic meaning of words and phrases. The larger the input to your embedding, the lower the quality the embedding will be. So, how should we approach long inputs?

One approach would be to use **chunking**. Chunking refers to the process of breaking down a particular source material into smaller pieces. Once your source material is appropriately chunked, you can embed each one and then store the embedding and the chunk together in a database (Upstash Vector in our case).

Using Upstash Vector, you can upsert embeddings generated from a custom embedding model, or you can directly upsert data, and Upstash Vector will generate embeddings for you.

In this guide, we demonstrate both methods—using Upstash-hosted embedding models and using a custom embedding model (e.g., OpenAI).

### Using Upstash Hosted Embedding Models

```typescript lib/ai/upstashVector.ts
import { Index } from '@upstash/vector'

// Configure Upstash Vector client
// Make sure UPSTASH_VECTOR_REST_URL and UPSTASH_VECTOR_REST_TOKEN are in your .env
const index = new Index({
  url: process.env.UPSTASH_VECTOR_REST_URL!,
  token: process.env.UPSTASH_VECTOR_REST_TOKEN!,
})

// Chunking logic: split on period
function generateChunks(input: string): string[] {
  return input
    .trim()
    .split('.')
    .filter(i => i !== '')
}

// Upsert
export async function upsertEmbedding(resourceId: string, content: string) {
  const chunks = generateChunks(content)

  // Convert each chunk into an Upstash upsert object
  const toUpsert = chunks.map((chunk, i) => ({
    id: `${resourceId}-${i}`,
    data: chunk, // Using the data field instead of vector because embeddings are generated by Upstash
    metadata: {
      resourceId,
      content: chunk, // Store the chunk as metadata to use during response generation
    },
  }))

  await index.upsert(toUpsert)
}

// Query
export async function findRelevantContent(query: string, k = 4) {
  const result = await index.query({
    data: query, // Again, using the data field instead of vector field
    topK: k,
    includeMetadata: true, // Fetch metadata as well
  })

  return result
}
```

So, in this file, we create a function to upsert data into our index, and another function to query our index. While upserting data, we chunk the content into smaller pieces and store those chunks in our index.

This approach is a lot simpler compared to using a custom embedding model, because we don't need to generate embeddings ourselves, Upstash does it for us.

### Using a Custom Embedding Model

Now, let's look at how we can use a custom embedding model. We will use OpenAI's `text-embedding-ada-002` embedding model.

```typescript lib/ai/upstashVector.ts
import { Index } from '@upstash/vector'
import { embed, embedMany } from 'ai'
import { openai } from '@ai-sdk/openai'

// Configure Upstash Vector client
const index = new Index({
  url: process.env.UPSTASH_VECTOR_REST_URL!,
  token: process.env.UPSTASH_VECTOR_REST_TOKEN!,
})

// Chunking logic: split on period
function generateChunks(input: string): string[] {
  return input
    .trim()
    .split('.')
    .filter(i => i !== '')
}

// Define the embedding model
const embeddingModel = openai.embedding('text-embedding-ada-002')

// Function to generate a single embedding
async function generateEmbedding(value: string): Promise<number[]> {
  const input = value.replaceAll('\\n', ' ')
  const { embedding } = await embed({
    model: embeddingModel,
    value: input,
  })
  return embedding
}

// Function to generate embeddings for multiple chunks
async function generateEmbeddings(
  value: string,
): Promise<Array<{ content: string; embedding: number[] }>> {
  const chunks = generateChunks(value)
  const { embeddings } = await embedMany({
    model: embeddingModel,
    values: chunks,
  })
  return embeddings.map((vector, i) => ({
    content: chunks[i],
    embedding: vector,
  }))
}

// Upsert
export async function upsertEmbeddings(resourceId: string, content: string) {
  // Generate embeddings for each chunk
  const chunkEmbeddings = await generateEmbeddings(content)
  // Convert each chunk into an Upstash upsert object
  const toUpsert = chunkEmbeddings.map((chunk, i) => ({
    id: `${resourceId}-${i}`, // e.g. "abc123-0"
    vector: chunk.embedding,
    metadata: {
      resourceId,
      content: chunk.content,
    },
  }))

  await index.upsert(toUpsert)
}

// Query
export async function findRelevantContent(query: string, k = 4) {
  const userEmbedding = await generateEmbedding(query)
  const result = await index.query({
    vector: userEmbedding,
    topK: k,
    includeMetadata: true,
  })

  return result
}
```

In this approach, we need to generate embeddings ourselves, which is an extra step. But the advantage is that we can use any embedding model we want.

OpenAI's `text-embedding-ada-002` generates embeddings with 1536 dimensions, so the index we created must have 1536 dimensions.

## Create Resource Server Action

We will create a server action to create a new resource and upsert it to the index. This will be used by our chatbot to store information.

```typescript lib/actions/resources.ts
'use server'

import { z } from 'zod'
import { upsertEmbeddings } from '@/lib/ai/upstashVector'

// A simple schema for incoming resource content
const NewResourceSchema = z.object({
  content: z.string().min(1),
})

// Server action to parse the input and upsert to the index
export async function createResource(input: { content: string }) {
  const { content } = NewResourceSchema.parse(input)

  // Generate a random ID
  const resourceId = crypto.randomUUID()

  // Upsert the chunks/embeddings to Upstash Vector
  await upsertEmbeddings(resourceId, content)

  return `Resource ${resourceId} created and embedded.`
}
```

## Chat API route

This route will act as the “backend” for our chatbot. The Vercel AI SDK’s useChat hook will, by default, POST to `/api/chat` with the conversation state. We’ll define that route and specify the AI model, system instructions, and any tools we’d like the model to use.

```typescript app/api/chat/route.ts
import { openai } from '@ai-sdk/openai'
import { streamText, tool } from 'ai'
import { z } from 'zod'

// Tools
import { createResource } from '@/lib/actions/resources'
import { findRelevantContent } from '@/lib/ai/upstashVector'

// Allow streaming responses up to 30 seconds
export const maxDuration = 30

export async function POST(req: Request) {
  const { messages } = await req.json()

  const result = streamText({
    // 1. Choose your AI model
    model: openai('gpt-4o'),

    // 2. Pass along the conversation messages from the user
    messages,

    // 3. Prompt the model
    system: `You are a helpful RAG assistant.
    You have the ability to add and retrieve content from your knowledge base.
    Only respond to the user with information found in your knowledge base.
    If no relevant information is found, respond with: "Sorry, I don't know."`,

    // 4. Provide your "tools": resource creation & retrieving content
    tools: {
      addResource: tool({
        description: `Add new content to the knowledge base.`,
        parameters: z.object({
          content: z.string().describe('The content to embed and store'),
        }),
        execute: async ({ content }) => {
          const msg = await createResource({ content })
          return msg
        },
      }),
      getInformation: tool({
        description: `Retrieve relevant knowledge from your knowledge base to answer user queries.`,
        parameters: z.object({
          question: z.string().describe('The question to search for'),
        }),
        execute: async ({ question }) => {
          const hits = await findRelevantContent(question)
          // Return array of metadata for each chunk
          // e.g. [{ id, score, metadata: { resourceId, content }}, ... ]
          return hits
        },
      }),
    },
  })

  // 5. Return the streaming response
  return result.toDataStreamResponse()
}
```

## Chat UI

Finally, we will implement our chat UI on the home page. We will use the Vercel AI SDK’s `useChat` hook to render the chat UI. By default, the Vercel AI SDK will POST to `/api/chat` on submit.

```typescript app/page.tsx
'use client'

import { useChat } from 'ai/react'

export default function Home() {
  // This hook handles message state + streaming from /api/chat
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    // You can enable multi-step calls if you want the model to call multiple tools in one session
    maxSteps: 3,
  })

  return (
    <div className="mx-auto max-w-md py-6">
      <h1 className="text-xl font-bold mb-4">RAG Chatbot with Upstash Vector</h1>

      {/* Render messages */}
      <div className="space-y-2 mb-8">
        {messages.map(m => (
          <div key={m.id} className="border p-2 rounded">
            <strong>{m.role}:</strong>
            <div>
              {/* If the model calls a tool, show which tool it called */}
              {m.content.length > 0 ? (
                m.content
              ) : (
                <i>calling tool: {m?.toolInvocations?.[0]?.toolName}</i>
              )}
            </div>
          </div>
        ))}
      </div>

      {/* Text input */}
      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          className="flex-1 border rounded px-2 py-1"
          placeholder="Say something..."
          value={input}
          onChange={handleInputChange}
        />
        <button className="px-4 py-1 bg-black text-white rounded" type="submit">
          Send
        </button>
      </form>
    </div>
  )
}
```

## Run the Chatbot

Now, we can run our chatbot with the following command:

```bash
npm run dev
```

Here is a screenshot of the chatbot in action:

  <img />

  <img />

  <img />

If you would like to see the entire code of a slightly revised version of this chatbot, you can check out the [GitHub repository](https://github.com/Abdusshh/rag-chatbot-ai-sdk). In this version, the user chooses which embedding model to use through the UI.

## Conclusion

Congratulations! You have successfully created a RAG chatbot that uses Upstash Vector to store and retrieve information. To learn more about Upstash Vector, please visit the [Upstash Vector documentation](/docs/vector).

To learn more about the AI SDK, visit the [Vercel AI SDK documentation](https://sdk.vercel.ai/docs/introduction). While creating this tutorial, we used the [RAG Chatbot guide](https://sdk.vercel.ai/docs/guides/rag-chatbot) created by Vercel, which uses PostgreSQL with pgvector as a vector database. Make sure to check it out if you want to learn how to create a RAG chatbot using pgvector.

# Flowise with Upstash Vector and Redis
Source: https://upstash.com/docs/vector/integrations/flowise

Flowise is an open source low-code tool for developers to build customized LLM orchestration flows & AI agents. With Upstash Vector and Upstash Redis, you can extend your Flowise flows to include semantic search, caching, and conversation memory.

## Install

To get started, you can install Flowise locally using npm. Run:

```bash
npm install -g flowise
```
Start Flowise:

```bash
npx flowise start
```

Open: http://localhost:3000

You also need to set up Upstash services:
1. Create a **Vector Index** in the [Upstash Console](https://console.upstash.com/vector). To learn more about index creation, you can check out [this page](https://docs.upstash.com/vector/overall/getstarted).
2. Create a **Redis Database** in the [Upstash Console](https://console.upstash.com/redis). To learn more about Redis database creation, you can check out [this page](/docs/redis/overall/getstarted).

## Nodes Overview

Flowise supports multiple Upstash integrations. Below are the nodes and their functionalities:

### 1. Upstash Vector Node

Use the **Upstash Vector** node to perform semantic search and store document embeddings. Connect the node to document loaders and embedding components for indexing and querying.

    <img width="400" />

### 2. Upstash Redis Cache Node

The **Upstash Redis Cache** node caches LLM responses in a serverless Redis database.

    <img width="400" />

### 3. Upstash Redis-Backed Chat Memory Node

The **Upstash Redis-Backed Chat Memory** node summarizes conversations and stores the memory in Redis. This enables persistent, context-aware interactions across multiple sessions.

    <img width="400" />

## Example Flow

Below is an example flow using Upstash Vector:

    <img />

## Learn More

For more details, visit the [Flowise documentation](https://docs.flowiseai.com/).

# LangChain with Upstash Vector
Source: https://upstash.com/docs/vector/integrations/langchain

You can use LangChain with Upstash Vector to perform semantic search and manage vector embeddings. LangChain is a powerful framework that integrates with vector databases, including Upstash Vector, making it easy to build intelligent applications.

First, we need to create a Vector Index in the [Upstash Console](https://console.upstash.com). To learn more about index creation, you can check out [this page](https://docs.upstash.com/vector/overall/getstarted).

## Install

```bash
pip install upstash-vector langchain langchain-community python-dotenv
```

## Usage

```python
from dotenv import load_dotenv
from langchain_community.vectorstores.upstash import UpstashVectorStore
from langchain.schema import Document

# Load environment variables
load_dotenv()

# Create a vector store instance
store = UpstashVectorStore(
    embedding=True,  # Embedding option enabled
)

# Sample documents to upload
documents = [
    Document(page_content="Upstash Vector is a scalable vector database."),
    Document(page_content="LangChain is a framework for building intelligent apps."),
    Document(page_content="Semantic search enables advanced query matching."),
]

# Add documents to the Upstash Vector index
store.add_documents(documents)

# Perform a similarity search
query = "What is LangChain?"
results = store.similarity_search(query, k=3)

print("Similarity Search Results:")
for res in results:
    print(res.page_content)
```

### Query Results

```plaintext
Similarity Search Results:
LangChain is a framework for building intelligent apps.
Semantic search enables advanced query matching.
Upstash Vector is a scalable vector database.
```

## Features

**Semantic Search**: Retrieve the most contextually relevant results using embeddings and vector similarity.

**Namespace Support**: Separate documents into different namespaces for better organization.

**Metada Filtering**: Metadata can be used to filter the results of a query.

## Notes

* Upstash Vector supports custom embeddings; you can specify an embedding model when initializing `UpstashVectorStore`.
* Use `.env` files to manage your Upstash credentials for secure and reusable configuration.

To learn more, visit the [LangChain documentation](https://python.langchain.com/docs/integrations/vectorstores/upstash/).

# Langflow with Upstash Vector
Source: https://upstash.com/docs/vector/integrations/langflow

Langflow provides an intuitive, visual interface to design LLM workflows. You can seamlessly integrate Upstash Vector into your Langflow projects to enable vector-based semantic search and context retrieval.

    <img />

## Install

To get started, install Langflow and Upstash Vector locally or use the Langflow dashboard from [DataStax](https://www.datastax.com/products/langflow). For local installation, run:

```bash
pip install langflow upstash-vector
```

## Usage

### Creating an Upstash Vector Index

Visit the [Upstash Console](https://console.upstash.com/vector) to create a vector index. To learn more about index creation, you can check out [this page](https://docs.upstash.com/vector/overall/getstarted).

### Adding Upstash Vector to Langflow

In Langflow, you can integrate Upstash Vector for document indexing and semantic search. Use the following steps:

1. Create a workflow with the **File**, **Split**, and **Upstash** components to process and store documents in the Upstash Vector index.
2. Perform a vector search by connecting the **Upstash** component to your query input.

    <img />

### Example Workflow

Enhance your chatbot by combining Langflow’s OpenAI integration with Upstash Vector. Create a RAG workflow to retrieve relevant context from your index and use it to answer user queries.

    <img />

## Learn More

For a detailed guide on building a RAG chatbot with Langflow and Upstash Vector, check out this [blog post](https://upstash.com/blog/langflow-upstash-vector).

# LlamaIndex with Upstash Vector
Source: https://upstash.com/docs/vector/integrations/llamaindex

You can use LlamaIndex with Upstash Vector to perform Retrieval-Augmented Generation (RAG). LlamaIndex is a powerful tool that integrates seamlessly with vector databases like Upstash Vector, enabling advanced query and response capabilities.

## Install

```bash
pip install llama-index upstash-vector llama-index-vector-stores-upstash python-dotenv
```

## Setup

First, create a Vector Index in the [Upstash Console](https://console.upstash.com). Configure the index with:
* **Dimensions**: 1536
* **Distance Metric**: Cosine

Once the index is created, copy the `UPSTASH_VECTOR_REST_URL` and `UPSTASH_VECTOR_REST_TOKEN` and add them to your `.env` file along with your OpenAI API key:

```
UPSTASH_VECTOR_REST_URL=your_upstash_url
UPSTASH_VECTOR_REST_TOKEN=your_upstash_token
OPENAI_API_KEY=your_openai_api_key
```

## Usage

Here’s how you can integrate LlamaIndex with Upstash Vector:

```python
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.vector_stores.upstash import UpstashVectorStore
from llama_index.core import StorageContext
import os
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

# Set OpenAI API key
openai.api_key = os.environ["OPENAI_API_KEY"]

# Initialize Upstash Vector store
upstash_vector_store = UpstashVectorStore(
    url=os.environ["UPSTASH_VECTOR_REST_URL"],
    token=os.environ["UPSTASH_VECTOR_REST_TOKEN"],
)

# Load documents using SimpleDirectoryReader
documents = SimpleDirectoryReader("./documents/").load_data()

# Create a storage context and initialize the index
storage_context = StorageContext.from_defaults(vector_store=upstash_vector_store)
index = VectorStoreIndex.from_documents(
    documents, storage_context=storage_context
)
```

## Querying

Once the index is created, you can query it to retrieve and generate responses based on document content.

```python
# Initialize the query engine
query_engine = index.as_query_engine()

# Perform queries
response_1 = query_engine.query("What is global warming?")
print(response_1)

response_2 = query_engine.query("How can we reduce our carbon footprint?")
print(response_2)
```

## Notes

* You can specify a namespace when creating the `UpstashVectorStore` instance:
  ```python
  vector_store = UpstashVectorStore(
      url="your_upstash_url",
      token="your_upstash_token",
      namespace="your_namespace"
  )
  ```

* Visit the [LlamaIndex documentation](https://docs.llamaindex.ai/en/latest) for more details.

# LlamaParse with Upstash Vector
Source: https://upstash.com/docs/vector/integrations/llamaparse

You can use LlamaParse with Upstash Vector to parse documents and perform semantic queries on the content. LlamaParse simplifies the extraction of structured information from files, which can then be indexed and queried using Upstash Vector.

## Install

```bash
pip install llama-index upstash-vector llama-index-vector-stores-upstash python-dotenv
```

## Setup

Create a Vector Index in the [Upstash Console](https://console.upstash.com). Set the index with:
* **Dimensions**: 1536
* **Distance Metric**: Cosine

Add the required environment variables to a `.env` file:

```
UPSTASH_VECTOR_REST_URL=your_upstash_url
UPSTASH_VECTOR_REST_TOKEN=your_upstash_token
LLAMA_CLOUD_API_KEY=your_llama_cloud_api_key
```

## Usage

### Parsing Documents

Use LlamaParse to parse a document. For example:

```python
from llama_parse import LlamaParse
from llama_index.core import SimpleDirectoryReader

# Initialize the parser
parser = LlamaParse(result_type="markdown")

# Parse a document
file_extractor = {".txt": parser}
documents = SimpleDirectoryReader(
    input_files=["./documents/global_warming.txt"],
    file_extractor=file_extractor
).load_data()
```

### Querying the Parsed Content

Once the document is parsed, you can index it using Upstash Vector and query its content:

```python
from llama_index.core import VectorStoreIndex
from llama_index.vector_stores.upstash import UpstashVectorStore
from llama_index.core import StorageContext
from dotenv import load_dotenv
import os

# Load environment variables
load_dotenv()

# Set up Upstash Vector Store
vector_store = UpstashVectorStore(
    url=os.getenv("UPSTASH_VECTOR_REST_URL"),
    token=os.getenv("UPSTASH_VECTOR_REST_TOKEN")
)

# Create storage context and index the parsed document
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)

# Perform a query
query_engine = index.as_query_engine()
response = query_engine.query("What is the main topic discussed in the document?")
```

To learn more, visit the [LlamaParse documentation](https://docs.cloud.llamaindex.ai/llamaparse/getting_started/get_an_api_key).

# Changelog
Source: https://upstash.com/docs/vector/overall/changelog

<Update label="August 2025">
* Introduced a new api to [rename a namespace](../api/endpoints/rename-namespace)
</Update>

<Update label="March 2025">
* Allow deleting vectors with [id prefix](../api/endpoints/delete#param-prefix)
  or [metadata filter](../api/endpoints/delete#param-filter).
* Allow [fetching](../api/endpoints/fetch#param-prefix)
  and [ranging](../api/endpoints/range#param-prefix) over vectors with id prefix.
</Update>

<Update label="February 2025">
* Introduced official SDK's for [PHP & Laravel](../sdks/php/getting-started).
</Update>

<Update label="January 2025">
* Introduced [Sparse and Hybrid indexes](https://upstash.com/blog/sparse-and-hybrid-indexes).
    * [Sparse Indexes](../features/sparseindexes) details.
    * [Hybrid Indexes](../features/hybridindexes) details.
</Update>

<Update label="September 2024">
* Introduced [resumable query/search](../features/resumablequery) functionality, allowing to initiate and continue queries across multiple api calls.
    * [Typescript SDK](../sdks/ts/commands/resumable-query)
    * [Python SDK](../sdks/py/example_calls/resumable-query)
* Added embedding latency charts to the Upstash Console vector usage page.
</Update>

<Update label="July 2024">
Added new [`HAS FIELD`](../features/filtering#has-field) and [`HAS NOT FIELD`](../features/filtering#has-not-field) metadata filtering operators.
</Update>

<Update label="June 2024">
* Implemented a new feature to store raw data in text format alongside metadata. See [Metadata and Data](../features/metadata#data).
* Added an API for updating vector, data, or metadata. It's also possible to update the metadata without overwriting all.
See [Update Vector](../api/endpoints/update) API.
* Added reset all namespaces API. See [Reset Namespace](../api/endpoints/reset) API.
* Improved query APIs to send a batch of queries in a single request. See [Query](../api/endpoints/query) API.
</Update>

<Update label="May 2024">
* Introduced [namespaces feature](/docs/vector/features/namespaces).
* Added GCP US-Central1 region.
</Update>

<Update label="April 2024">
Added option to upsert and query raw data using [Upstash embedding service](/docs/vector/features/embeddingmodels).
</Update>

<Update label="February 2024">
Implemented metadata filtering with SQL-like syntax. See [Metadata Filtering](/docs/vector/features/filtering).
</Update>

<Update label="January 2024">
[Initial release!](https://upstash.com/blog/introducing-vector-database)
</Update>

# Compare
Source: https://upstash.com/docs/vector/overall/compare

// todo melek

# Getting Started
Source: https://upstash.com/docs/vector/overall/getstarted

import { Catalog } from "/snippets/catalog.jsx";

<Check >
**Prerequisite**

You need an Upstash account before creating a vector, create one
[here](https://console.upstash.com).

</Check>

## Create an Index

Once you logged in, you can create a Vector Index by clicking on the `Create Index` button in the Vector tab.

  <img />

**Name:** Type a name for your index.

**Region:** Choose the region for your index. For optimal performance, select the region closest to your applications. We plan to support additional regions and cloud providers. Feel free to send your requests to [support@upstash.com](mailto:support@upstash.com)

**Type:** The type of index: Dense, [Sparse](/docs/vector/features/sparseindexes) or [Hybrid](/docs/vector/features/hybridindexes). For semantic search, you can prefer dense. For full text (or keyword) search, you can prefer sparse. If you need a combination, you can choose hybrid.

If you choose Dense or Hybrid as index type, you will also be presented with options to select the dimensions and distance metric of your index.

<Tip>
  For the purpose of using the code samples on this page, you can create a dense index with `dimension: 2`. Distance metric can be any of the options.
</Tip>

Once you pick these options, you will choose a plan:

  <img  />

**Free:** The free plan is suitable for small projects. It has a limit of 10,000 queries and 10,000 updates daily.

**Pay as You Go:** Pay as you go plan is a flexible plan with per-request-pricing. It is suitable for projects with unpredictable traffic.

**Fixed:** Fixed plan is suitable for projects with predictable traffic. It has a fixed monthly price with 1M query and 1M updates daily.

**Pro:** Pro plan is suitable for projects with high traffic and storage needs. It has a fixed monthly price with extra security and isolation features.

**Enterprise:** If you plan to have over a billion vectors then Enterprise plan is for you. It has a fixed monthly price with extra security and isolation features. Contact us at [sales@upstash.com](mailto:sales@upstash.com) for more information.

## Insert Index

You can access data in your index using REST API or our SDKs. You can copy the sample code from the `Connect` section in the console.

<Tabs>

<Tab title="Python">
```python
from upstash_vector import Index

index = Index(url="UPSTASH_VECTOR_REST_URL", token="UPSTASH_VECTOR_REST_TOKEN")

index.upsert(
  vectors=[
    ("1", [0.6, 0.8], {"field": "value"}),
  ]
)
```
</Tab>

<Tab title="JavaScript">
```ts
import { Index } from "@upstash/vector";

const index = new Index({
    url: "UPSTASH_VECTOR_REST_URL",
    token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.upsert({ id: "1", vector: [0.6, 0.8], metadata: {field: "value"} })
```
</Tab>

<Tab title="Go">
```go
import "github.com/upstash/vector-go"

func main() {
  index := vector.NewIndex("UPSTASH_VECTOR_REST_URL", "UPSTASH_VECTOR_REST_TOKEN")

  index.Upsert(vector.Upsert{
	  Id:       "1",
	  Vector:   []float32{0.6, 0.8},
	  Metadata: map[string]any{"field": "value"},
  })
}
```
</Tab>

<Tab title="PHP">
```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorUpsert;

$index = new Index(
  url: 'UPSTASH_VECTOR_REST_URL',
  token: 'UPSTASH_VECTOR_REST_TOKEN',
);

$index->upsert(new VectorUpsert(
  id: '1',
  vector: [0.6, 0.8],
  metadata: ['field' => 'value'],
));
```
</Tab>

<Tab title="curl">
```shell
curl $UPSTASH_VECTOR_REST_URL/upsert \
  -X POST \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"id": "1", "vector": [0.6, 0.8], "metadata": {"field": "value"}}'
```
</Tab>

</Tabs>

## Query Index
You can perform a similarity search by providing a query vector as a parameter. The dimension of the query vector must match the dimension of your index. Also, you can query by metadata filtering.

<Note>
Upstash is eventually consistent, so there may be a delay before the newly inserted or updated vectors are ready for querying.
</Note>

<Tabs>

<Tab title="Python">
```python
from upstash_vector import Index

index = Index(url="UPSTASH_VECTOR_REST_URL", token="UPSTASH_VECTOR_REST_TOKEN")

index.query(
    vector=[0.6, 0.8],
    top_k=3,
    include_metadata=True,
)
```
</Tab>

<Tab title="JavaScript">
```ts
import { Index } from "@upstash/vector";

const index = new Index({
  url: "UPSTASH_VECTOR_REST_URL",
  token: "UPSTASH_VECTOR_REST_TOKEN",
})

await index.query({ vector: [0.6, 0.8], topK: 3, includeMetadata: true })
```
</Tab>

<Tab title="Go">
```go
import "github.com/upstash/vector-go"

func main() {
  index := vector.NewIndex("UPSTASH_VECTOR_REST_URL", "UPSTASH_VECTOR_REST_TOKEN")

  index.Query(vector.Query{
	  Vector:          []float32{0.6, 0.8},
	  TopK:            3,
	  IncludeMetadata: true,
  })
}
```
</Tab>

<Tab title="PHP">
```php
use Upstash\Vector\Index;
use Upstash\Vector\VectorQuery;

$index = new Index(
  url: '<UPSTASH_VECTOR_REST_URL>',
  token: '<UPSTASH_VECTOR_REST_TOKEN>',
);

$index->query(new VectorQuery(
  vector: [0.6, 0.8],
  topK: 3,
  includeMetadata: true,
));
```
</Tab>

<Tab title="curl">
```shell
curl $UPSTASH_VECTOR_REST_URL/query \
  -H "Authorization: Bearer $UPSTASH_VECTOR_REST_TOKEN" \
  -d '{"vector": [0.6, 0.8], "topK": 3, "includeMetadata": "true"}'
```
</Tab>

</Tabs>

## Usage and Data Browser

In Upstash console, you can see the charts of your index:

  <img />

 There are following charts:

* **Daily Requests:** The number of queries and updates to your index in the last 5 days.
* **Throughput:** The number of queries and updates to your index in the selected time period.
* **Latency:** The mean and P99 latency of queries and updates to your index in the selected time period.
* **Vector Count:** The number of vectors in your index in the selected time period.
* **Data Size:** The size of your index in the selected time period.

You can also query your index with a simple UI:

  <img />

## SDKs

<Catalog product="vector" type="sdk" cols={3} />

# llms.txt
Source: https://upstash.com/docs/vector/overall/llms-txt

# Pricing & Limits
Source: https://upstash.com/docs/vector/overall/pricing

Please check our [pricing page](https://upstash.com/pricing/vector) for the most up-to-date information on pricing and limits.

# What is Upstash Vector?
Source: https://upstash.com/docs/vector/overall/whatisvector

Upstash Vector is a **serverless vector database designed for high-performance vector search at scale**. It powers advanced systems in areas such as:

* Natural Language Processing (NLP)
* Retrieval Augmented Generation (RAG)
* Recommendation engines
* Image recognition
* Clustering or classification

... without any infrastructure management.

***

## What is a Vector Database?

Vector databases are specifically designed to store and efficiently search high-dimensional vectors. These vectors can represent images, sounds, text, or other media for powerful similarity search across content that traditional databases struggle to represent properly.

Their specialization makes vector stores the perfect foundation for similarity-based search. Using specialized approximation algorithms such as Approximate Nearest Neighbor (ANN), they can typically provide much higher indexing and query performance than general-purpose databases or extensions like pgvector.

***

## Upstash Vector Core Features:

* **High-Performance Queries:** Upstash Vector is powered by DiskANN[1], a highly efficient approximation algorithm that delivers very high recall rates (resulting in better output quality) and ultra-low latency. See [ANN Search Algorithm](../features/algorithm) for more insight into the technology behind our vector database.

* **Batteries Included:** A convenient REST API and SDKs with first-class Python and TypeScript support.

* **Serverless Pricing:** Upstash Vector is fully managed and serverless, we take care of the hosting and high availability complexities. You only pay for what you actually use. See our [Vector Pricing Page](https://upstash.com/pricing/vector) for more details.

* **Multiple Similarity Functions:** We natively support similarity functions such as Euclidean distance, cosine similarity, and dot product.
  See [Vector Similarity](../features/similarityfunctions) for more details.

* **Metadata Support:** Attach metadata to vectors to store additional information or context. When querying an index, you can then include a metadata filter to limit the search to records that match a filter expression. See [Metadata Documentation](../features/metadata) for more details.

***

### References

1. Subramanya, S. J., Devvrit, Kadekodi, R., Krishaswamy, R., Simhadri, H. V. (2019). _DiskANN: Fast Accurate Billion-Point Nearest Neighbor Search on a Single Node_. In Proceedings of the 33rd International Conference on Neural Information Processing Systems (NeurIPS '19), Article No.: 1233, Pages 13766–13776. [https://dl.acm.org/doi/abs/10.5555/3454287.3455520]

# Go SDK
Source: https://upstash.com/docs/vector/sdk/gosdk

# Semantic Cache JS
Source: https://upstash.com/docs/vector/sdk/semantic-cache-js

# Semantic Cache Python
Source: https://upstash.com/docs/vector/sdk/semantic-cache-py

- [Deleting Vectors](https://upstash.com/docs/vector/sdks/php/commands/delete-vectors.md)
- [Fetching Vectors](https://upstash.com/docs/vector/sdks/php/commands/fetch.md)
- [Info](https://upstash.com/docs/vector/sdks/php/commands/info.md)
- [Querying Vectors](https://upstash.com/docs/vector/sdks/php/commands/query.md)
- [Reset](https://upstash.com/docs/vector/sdks/php/commands/reset.md)
- [Upserting Data with Embedding Models](https://upstash.com/docs/vector/sdks/php/commands/upsert-data.md)
- [Upserting Vectors](https://upstash.com/docs/vector/sdks/php/commands/upsert-vectors.md)

# Getting Started
Source: https://upstash.com/docs/vector/sdks/php/getting-started

`upstash/vector` is a PHP SDK for Upstash Vector, enabling easier operations on Vector Store.

Using `upstash/vector` you can:

* Upsert a vector with metadata to an index.
* Fetching the vectors with specified IDs.
* Querying a vector over pre-defined embeddings.
* Remove vectors from an index.
* Access index stats.
* Reset everything related to an index.

You can find the Github Repository [here](https://github.com/upstash/vector-php).

## Install

To install the SDK, you can use composer:

```shell composer
composer require upstash/vector
```

<Note>
We also built a Laravel package that you can use to integrate the SDK with your Laravel application.
[Learn more about our Laravel SDK](./laravel)
</Note>

## Usage

### Initializing the client

There are two pieces of configuration required to use the Upstash vector client: a REST token and REST URL. These values can be passed using environment variables or in code through the initialization of the Index. Find your configuration values in the console dashboard at [https://console.upstash.com/](https://console.upstash.com/).

#### Using environment variables

The environment variables used to configure the client are the following. You can follow [this guide](/docs/vector/overall/getstarted) to retrieve credentials.

```bash
UPSTASH_VECTOR_REST_URL="your_rest_url"
UPSTASH_VECTOR_REST_TOKEN="your_rest_token"
```

When these environment variables are set, you can initialize the client from the environment.

```php
use Upstash\Vector\Index;

$index = Index::fromEnv();
```

#### Manual Initialization

If you prefer to pass these values in code, the constructor accepts as parameters the `url` and `token` values. This
could be useful if your application needs to interact with multiple projects, each with a different configuration.

```php
use Upstash\Vector\Index;

$index = new Index(
  url: "<UPSTASH_VECTOR_REST_URL>",
  token: "<UPSTASH_VECTOR_REST_TOKEN>",
);
```

# Getting Started with Laravel
Source: https://upstash.com/docs/vector/sdks/php/laravel

`upstash/vector-laravel` is a dedicated Laravel SDK for Upstash Vector, that is built on top of our official PHP SDK.

By using `upstash/vector-laravel` you will be able to:

* Integrate Upstash Vector by only installing the package and setting environment variables.
* A native Vector facade to interact with your vector database.
* Able to maintain multiple index connections in your application.

You can find the Github Repository [here](https://github.com/upstash/vector-laravel).

## Install

To install the SDK, you can use composer:

```shell composer
composer require upstash/vector-laravel
```

## Setup

There are two pieces of configuration required to use the Upstash vector client: a REST token and REST URL. These values can be passed using environment variables or in code through the initialization of the Index. Find your configuration values in the console dashboard at [https://console.upstash.com/](https://console.upstash.com/).

```bash
UPSTASH_VECTOR_REST_URL="your_rest_url"
UPSTASH_VECTOR_REST_TOKEN="your_rest_token"
```

## Usage

Our Laravel SDK will configure your index into the Laravel Service Container. You can access it by calling the `Vector` facade
or by injecting the `IndexInterface` into your controllers.

### Vector Facade

When these environment variables are set, you can start using your Index by calling the `Vector` facade.

```php
use Upstash\Vector\Laravel\Facades\Vector;

Vector::getInfo(); // Fetches the index info.
```

### Dependency Injection

If you prefer to avoid using the facade, you can inject the `IndexInterface` into your controllers.

```php
namespace App\Http\Controllers;

use Upstash\Vector\Contracts\IndexInterface;

class Controller
{
    public function index(IndexInterface $index)
    {
        $namespaces = $index->listNamespaces();

        return response()->json(['namespaces' => $namespaces]);
    }
}
```

## Configuration

You can also configure the SDK to be able to use multiple indexes in your application.

For doing that you can publish the configuration file by running the following command:
```shell
php artisan vendor:publish --tag="vector-config"
```

You'll get a new file under `config/vector.php` that you can edit to add your indexes.

```php
return [
    'default' => env('UPSTASH_VECTOR_CONNECTION', 'default'),

    'connections' => [
        'default' => [
            'url' => env('UPSTASH_VECTOR_REST_URL'),
            'token' => env('UPSTASH_VECTOR_REST_TOKEN'),
        ],
    ],
];
```

### Multiple Connections

If you want to use multiple connections in your application, you can add them to the `connections` array as shown below:

```php
return [
    'default' => env('UPSTASH_VECTOR_CONNECTION', 'default'),

    'connections' => [
        'default' => [
            'url' => env('UPSTASH_VECTOR_REST_URL'),
            'token' => env('UPSTASH_VECTOR_REST_TOKEN'),
        ],
        'another' => [
            'url' => env('SECOND_UPSTASH_VECTOR_REST_URL'),
            'token' => env('SECOND_UPSTASH_VECTOR_REST_TOKEN'),
        ],
    ],
];
```

To access a specific connection, you can use the `connection` method:

```php
use Upstash\Vector\Laravel\Facades\Vector;

Vector::connection('another')->getInfo();
```

# Delete
Source: https://upstash.com/docs/vector/sdks/py/example_calls/delete

## Method

The `delete` method allows you to remove vectors from the index based on their identifiers.
The command accepts the following parameters:

* `ids`: A list of identifiers of vectors to be deleted.
* `prefix`: A string prefix to match vector IDs. All vectors with IDs that start with this prefix will be deleted.
* `filter`: A metadata filter to match vectors to be deleted.

<Note>Only one of `ids`, `prefix`, or `filter` can be provided.</Note>

It returns the following field in response:

* `deleted`: An integer indicating how many vectors were deleted with the command.

## Delete Example

```python
from upstash_vector import Index

index = Index.from_env()

# Specify the identifiers of vectors to be deleted
ids_to_delete = ["id1", "id2", "id3"]

# Delete the specified vectors
delete_result = index.delete(ids=ids_to_delete)

# Display the number of vectors deleted
print("Number of Vectors Deleted:", delete_result.deleted)
```

Alternatively, you can delete a singular vector:

```python
index.delete("id-4")
```

Also, you can specify a namespace to operate on. When no namespace
is provided, the default namespace will be used.

```python
index.delete("id-4", namespace="ns")
```

## Delete with metadata filter

This will delete all vectors with metadata that matches the provided filter. For more information, see [Metadata Filtering](/docs/vector/features/filtering).

```python
index.delete(filter="age > 30")
```

## Delete with id prefix

This will delete all vectors with IDs that start with the prefix.

```python
index.delete(prefix="id-")
```

# Fetch
Source: https://upstash.com/docs/vector/sdks/py/example_calls/fetch

## Method

The `fetch` method allows you to retrieve vectors from the index based on their identifiers. It takes the following input parameters:

* `ids`: A string or a list of strings representing the identifiers of the vectors to be fetched.
* `prefix`: A string prefix to match vector IDs. All vectors with IDs that start with this prefix will be retrieved.
* `include_vectors`: A boolean flag indicating whether to include vectors in the fetch results.
* `include_metadata`: A boolean flag indicating whether to include metadata in the fetch results.
* `include_data`: A boolean flag indicating whether to include data in the fetch results.
* `namespace`: The namespace to use. When not specified, the default namespace is used.

As a response, following field is returned:

* `vectors`: A list containing information for each fetched vector, including `id`, `vector`, `sparse_vector`, `metadata`, and `data`.

## Fetch Example

```python
from upstash_vector import Index

index = Index.from_env()

# Specify the identifiers of vectors to be fetched
ids_to_fetch = ["id-1", "id-2", "id-3"]

# Fetch the specified vectors with vectors and metadata included
fetch_result = index.fetch(
    ids=ids_to_fetch,
    include_vectors=True,
    include_metadata=True,
    include_data=True,
)

# Display the fetched vectors
for vector_info in fetch_result:
    print("ID:", vector_info.id)
    print("Vector:", vector_info.vector)
    print("Metadata:", vector_info.metadata)
    print("Data:", vector_info.data)
```

Alternatively, you can fetch a singular vector:

```python
index.fetch("id-4")
```

Also, you can specify a namespace to operate on. When no namespace
is provided, the default namespace will be used.

```python
index.fetch("id-4", namespace="ns")
```

## Fetch with id prefix

This will fetch all vectors with IDs that start with the prefix.

<Warning>
  For fetching larger datasets with id prefix, prefer using the paginated
  `range` command to prevent timeouts.
</Warning>

```python
index.fetch(prefix="id-")
```

# Info
Source: https://upstash.com/docs/vector/sdks/py/example_calls/info

## Method

The `info` method provides statistical information about the index, returning the following fields:

* `vector_count`: The total number of vectors in the index.
* `pending_vector_count`: The number of vectors that are currently pending (not yet fully processed).
* `index_size`: The size of the index in bytes.
* `dimension`: How many dimensions the index has
* `similarity_function`: Similarity function chosen for the index
* `namespaces`: Map of namespace names of the index to their statistics.

## Info Example

```python
from upstash_vector import Index

index = Index.from_env()

from upstash_vector import Index

index = Index.from_env()

# Get statistical information about the index
info_result = index.info()

# Display the info result
print("Vector Count:", info_result.vector_count)
print("Pending Vector Count:", info_result.pending_vector_count)
print("Index Size:", info_result.index_size)
print("Dimension:", info_result.dimension)
print("Similarity Function:", info_result.similarity_function)

for ns, ns_info in info_result.namespaces.items():
    print("Namespace:", ns, "Vector Count:", ns_info.vector_count)
    print("Namespace:", ns, "Pending Vector Count:", ns_info.pending_vector_count)
```

# Query
Source: https://upstash.com/docs/vector/sdks/py/example_calls/query

## Method

To retrieve vectors from the index based on specific criteria, you can use the `query` method, which accepts the following parameters:

* `vector`: The reference vector for similarity comparison.
* `sparse_vector`: The sparse vector value to query.
* `data`: A string for text-based queries (mutually exclusive with vector).
* `include_metadata`: A boolean flag indicating whether to include metadata in the query results.
* `include_vector`: A boolean flag indicating whether to include vectors in the query results.
* `include_data`: A boolean flag indicating whether to include data in the query results.
* `top_k`: The number of top matching vectors to retrieve.
* `filter`: Metadata filtering of the vector is used to query your data based on the filters and narrow down the query results.
* `namespace`: The namespace to use. When not specified, the default namespace is used.
* `weighting_strategy`: Weighting strategy to be used for sparse vectors.
* `fusion_algorithm`: Fusion algorithm to use while fusing scores from hybrid vectors.
* `query_mode`: Query mode for hybrid indexes with Upstash-hosted embedding models.

As response, the object has the following fields:

* `id`: The identifier associated with the matching vector.
* `metadata`: Additional information or attributes linked to the matching vector.
* `score`: A measure of similarity indicating how closely the vector matches the query vector. The score is normalized to the range [0, 1], where 1 indicates a perfect match.
* `vector`: The vector itself (included only if `include_vector` is set to `True`).
* `sparse_vector`: The sparse vector itself (included only if `include_vector` is set to `True`).
* `data`: Additional unstructured information linked to the matching vector.

<Tip>If you wanna learn more about filtering check: [Metadata Filtering](/docs/vector/features/filtering)</Tip>

## Query Example

```python
import random

from upstash_vector import Index

index = Index.from_env()

# Generate a random vector for similarity comparison
dimension = 128  # Adjust based on your index's dimension
query_vector = [random.random() for _ in range(dimension)]

# Execute the query
query_result = index.query(
    vector=query_vector,
    include_metadata=True,
    include_data=True,
    include_vectors=False,
    top_k=5,
    filter="genre = 'fantasy' and title = 'Lord of the Rings'",
)

# Print the query result
for result in query_result:
    print("Score:", result.score)
    print("ID:", result.id)
    print("Vector:", result.vector)
    print("Metadata:", result.metadata)
    print("Data:", result.data)
```

## Batch Query Method

It is also possible to perform a batch of queries in a single
call to eliminate round trips to server.

## Batch Query Example

```python
import random

from upstash_vector import Index

index = Index.from_env()

# Generate a random vector for similarity comparison
dimension = 128  # Adjust based on your index's dimension
query_vectors = [[random.random() for _ in range(dimension)] for _ in range(2)]

# Execute the query
query_results = index.query_many(
    queries=[
        {
            "vector": query_vectors[0],
            "include_metadata": True,
            "include_data": True,
            "include_vectors": False,
            "top_k": 5,
            "filter": "genre = 'fantasy' and title = 'Lord of the Rings'",
        },
        {
            "vector": query_vectors[1],
            "include_metadata": False,
            "include_data": False,
            "include_vectors": True,
            "top_k": 3,
            "filter": "genre = 'drama'",
        },
    ]
)

for i, query_result in enumerate(query_results):
    print(f"Query-{i} result:")

    # Print the query result
    for result in query_result:
        print("Score:", result.score)
        print("ID:", result.id)
        print("Vector:", result.vector)
        print("Metadata:", result.metadata)
        print("Data:", result.data)
```

Also, you can specify a namespace to operate on. When no namespace
is provided, the default namespace will be used.

```python
index.query(..., namespace="ns")
```

# Range
Source: https://upstash.com/docs/vector/sdks/py/example_calls/range

## Method

The `range` method allows you to retrieve vectors from the index within a specified range. The function accepts the following parameters:

* `cursor`: A cursor to start the range query.
* `prefix`: A string prefix to match vector IDs. All vectors with IDs that start with this prefix will be retrieved.
* `limit`: The maximum number of vectors to retrieve in a single query.
* `include_vectors`: A boolean flag indicating whether to include vectors in the range results.
* `include_metadata`: A boolean flag indicating whether to include metadata in the range results.
* `include_data`: A boolean flag indicating whether to include data in the range results.

As response, the object has the following fields:

* `next_cursor`: A cursor indicating the position to start the next range query. If `""`, there are no more results.
* `vectors`: A list containing information for each vector, including `id`, `vector`, and `metadata`.

<Note>
  The range command is stateless, meaning you need to pass all of the parameters
  in each subsequent request.
</Note>

## Range Example

```python
from upstash_vector import Index

index = Index.from_env()

# Execute the range query
range_result = index.range(
    cursor="",
    limit=10,
    include_vectors=False,
    include_metadata=True,
    include_data=True,
)

# Print the range result
print("Next Cursor:", range_result.next_cursor)

for vector_info in range_result.vectors:
    print("ID:", vector_info.id)
    print("Vector:", vector_info.vector)
    print("Metadata:", vector_info.metadata)
    print("Data:", vector_info.data)
```

## Range with id prefix

This will retrieve all vectors with IDs that start with the prefix.

```python
index.range(prefix="id-")
```

## Scanning Whole Index

For scanning the entire index, you can use a similar loop as shown below:

```python
res = index.range(cursor="", limit=5)
print(res.vectors)

while res.next_cursor != "":
    res = index.range(cursor=res.next_cursor, limit=10)
    print(res.vectors)
```

Also, you can specify a namespace to operate on. When no namespace
is provided, the default namespace will be used.

```python
index.range(..., namespace="ns")
```

# Reset
Source: https://upstash.com/docs/vector/sdks/py/example_calls/reset

## Method

The `reset` method allows you to clear all vectors and metadata from a particular
namespace or all namespaces of an index.

## Reset Example

Resets the default namespace.

```python
from upstash_vector import Index
index = Index.from_env()

index.reset()
```

## Reset Namespace Example

Resets the given namespace.

```python
from upstash_vector import Index
index = Index.from_env()

index.reset(namespace="ns")
```

## Reset All Namespaces

Resets all the namespaces of an index.

```python
from upstash_vector import Index
index = Index.from_env()

index.reset(all=True)
```

# Resumable Query
Source: https://upstash.com/docs/vector/sdks/py/example_calls/resumable-query

Resumable queries let you start a nearest-neighbor search and continue fetching
additional results later without restarting the search. This is useful for
pagination or when you want to stream results incrementally instead of loading
the whole result set into memory.

Quick example (sync):

```python
from upstash_vector import Index

index = Index()

# start a resumable query (returns initial results and a handle)
results, handle = index.resumable_query(
    vector=[0.1, 0.2],
    top_k=2,
    include_metadata=True,
    include_vectors=True,
    namespace="example-namespace",
)

with handle:
    # `results` contains the first batch
    for r in results:
        print(r.id, r.metadata, getattr(r, "vector", None))

    # fetch more results (fetch_next returns a list)
    more = handle.fetch_next(3)
    for r in more:
        print(r.id)

    # when the context block exits the handle is stopped automatically
```

Parameters

* vector (List[float] | SupportsToList | None) — query vector (mutually exclusive with `data`).
* top_k (int, default 10) — how many top matches to return in the initial batch.
* include_vectors (bool, default False) — include full vector values on results.
* include_metadata (bool, default False) — include metadata on results.
* filter (str, default "") — filter expression to narrow results by metadata.
* data (str | None) — text query for indexes using Upstash-hosted embedding models.
* namespace (str, default DEFAULT_NAMESPACE) — namespace to search in.
* include_data (bool, default False) — include stored `data` field (used for embedding indexes).
* max_idle (int, default 3600) — how long the server keeps the resumable query alive (seconds).
* sparse_vector (SparseVector | TupleAsSparseVectorT | None) — sparse vector for sparse/hybrid indexes.
* weighting_strategy (WeightingStrategy | None) — weighting strategy for sparse vectors.
* fusion_algorithm (FusionAlgorithm | None) — fusion algorithm for hybrid scoring.
* query_mode (QueryMode | None) — query mode for hybrid embedding indexes (e.g. SPARSE).

How it works

* The call to `resumable_query` returns a tuple `(result, handle)` where `result`
  is the first batch (list of QueryResult) and `handle` is a `ResumableQueryHandle`.
* Use `handle.fetch_next(n)` (or `await handle.fetch_next(n)`) to retrieve the next
  `n` results. If no more results are available an empty list is returned.
* Always stop the handle when finished (use `with handle:` / `async with handle:` or
  call `handle.stop()` / `await handle.stop()`). After the handle is stopped, further
  calls to `fetch_next` or `stop` raise an error (tests expect UpstashError).

Examples

Simple paging (sync)

```python
results, handle = index.resumable_query(vector=[0.1, 0.2], top_k=2, namespace="ns")

with handle:
    all_results = list(results)
    while True:
        next_batch = handle.fetch_next(2)
        if not next_batch:
            break
        all_results.extend(next_batch)

# handle is stopped after exiting the context manager
```

Sparse / hybrid example (showing advanced options)

```python
from upstash_vector.types import SparseVector, WeightingStrategy, FusionAlgorithm

scores, handle = index.resumable_query(
    vector=[0.1, 0.1],
    sparse_vector=([0], [0.1]),
    top_k=2,
    include_vectors=True,
    include_metadata=True,
    include_data=True,
    weighting_strategy=WeightingStrategy.IDF,
    fusion_algorithm=FusionAlgorithm.DBSF,
    namespace="hybrid-ns",
)

with handle:
    for s in scores:
        print(s.id, getattr(s, "vector", None), getattr(s, "sparse_vector", None))
    more = handle.fetch_next(1)
    print("more:", more)
```

Notes

* The server enforces a limit on the number of active resumable queries; keep them
  short-lived and call `stop()` when finished. The default `max_idle` is 3600 seconds.
* After calling `stop()` (explicitly or via context manager), further `fetch_next` or
  `stop` calls will raise an error.

# Update
Source: https://upstash.com/docs/vector/sdks/py/example_calls/update

## Methods

The `update` method enables you to update the `vector`, `metadata`, or `data`
of a vector.

## Update Example

```python
from upstash_vector import Index

index = Index.from_env()

updated = index.update(
    id="id1",
    metadata={"new": "metadata"},
    data="new-data",
)

print(updated)
```

## Patch Metadata Example

It is also possible to patch metadata (update or delete existing fields or
set new fields) according the [JSON Merge Patch](https://datatracker.ietf.org/doc/html/rfc7386) algorithm.

```python
from upstash_vector import Index
from upstash_vector.types import MetadataUpdateMode

index = Index.from_env()

updated = index.update(
    id="id2",
    metadata={
        "existing-field": "new-value",
        "existing-field-to-delete": None,
        "new-field": "new-value",
    },
    metadata_update_mode=MetadataUpdateMode.PATCH,
)

print(updated)
```

Also, you can specify a namespace to operate on. When no namespace
is provided, the default namespace will be used.

```python
index.update(..., namespace="ns")
```

# Upsert
Source: https://upstash.com/docs/vector/sdks/py/example_calls/upsert

## Methods
The `upsert` method enables you to insert or update vectors in the index.
You can perform upsert operations in three ways: using a vector object, a tuple, or a dictionary.

### Upsert Via Vector Object

```python
import random

from upstash_vector import Index, Vector

index = Index.from_env()

dimension = 128  # Adjust based on your index's dimension
upsert_amount = 100

vectors = [
    Vector(
        id=f"generated-id-{i}",
        vector=[random.random() for _ in range(dimension)],
        metadata={"some_field": f"some_value-{i}"},
        data=f"some-unstructured-data-{i}",
    )
    for i in range(upsert_amount)
]

index.upsert(vectors=vectors)
```

### Upsert Via Tuple

```python
import random

from upstash_vector import Index

index = Index.from_env()

dimension = 128  # Adjust based on your index's dimension
upsert_amount = 100

vectors = [
    (
        f"generated-id-{i}",
        [random.random() for _ in range(dimension)],
        {"some_field": f"some_value-{i}"},
        f"some-unstructured-data-{i}",
    )
    for i in range(upsert_amount)
]

index.upsert(vectors=vectors)
```

### Upsert Via Dictionary

```python
import random

from upstash_vector import Index

index = Index.from_env()

dimension = 128  # Adjust based on your index's dimension
upsert_amount = 100

vectors = [
    {
        "id": f"generated-id-{i}",
        "vector": [random.random() for _ in range(dimension)],
        "metadata": {"some_field": f"some_value-{i}"},
        "data": f"some-unstructured-data-{i}",
    }
    for i in range(upsert_amount)
]

index.upsert(vectors=vectors)
```

Also, you can specify a namespace to operate on. When no namespace
is provided, the default namespace will be used.

```python
index.upsert(..., namespace="ns")
```

# Features
Source: https://upstash.com/docs/vector/sdks/py/features

# Retry Mechanism

The `upstash-vector` SDK incorporates a reliable retry mechanism to manage network or API issues.
In the event of a failed request, the SDK automatically attempts up to three retries,
with each attempt spaced one second apart.

For customization of the retry behavior, you have the flexibility to set the `retries` and
`retry_interval` (in seconds) parameters according to your specific requirements.
For instance:

```python
from upstash_vector import Index

# Try 5 times with a 2-second interval between retries
index = Index.from_env(retries=5, retry_interval=2.0)
```

# Telemetry

This library sends anonymous telemetry data to help us improve your experience.
We collect the following:

* SDK version
* Platform (Vercel, AWS)
* Python Runtime version

You can opt out by passing `allow_telemetry=False` when initializing the Redis client:

```py
idx = Index("INDEX_URL", "INDEX_TOKEN", allow_telemetry=False)
```

# Getting Started
Source: https://upstash.com/docs/vector/sdks/py/gettingstarted

The `upstash-vector` SDK is a lightweight, HTTP-based Upstash Vector client designed for Python. It seamlessly operates in both serverless and serverful environments, ensuring optimal compatibility across various connection setups.

This SDK simplifies interaction with Upstash Vector through the [Upstash Vector API](https://docs.upstash.com/vector/api/get-started).

It is designed to work with Python versions 3.8 and above.

Explore the source code, contribute, and stay informed through our [GitHub Repository](https://github.com/upstash/vector-py).

## Install

To begin using `upstash-vector`, you can install it via PyPI using the following command:

```bash
pip install upstash-vector
```

## Usage

Before using upstash-vector, you'll need to set up a vector database on [Upstash](https://console.upstash.com/). Once created, grab your URL and TOKEN from the Upstash console.

To initialize the index client:

```python
from upstash_vector import Index
index = Index(url="UPSTASH_VECTOR_REST_URL", token="UPSTASH_VECTOR_REST_TOKEN")
```

Alternatively, you can automatically load the credentials from the environment:

```python
from upstash_vector import Index
index = Index.from_env()
```

For serverless environments that allow it, it's recommended to initialize the client outside the request handler to be reused while your function is still "hot."

Here's an example of how you can use the SDK in your Python application:

```python
import random
from upstash_vector import Index

# Initialize the index client using environment variables
index = Index.from_env()

def main():
    # Define the dimension based on the index configuration
    dimension = 128
    # Generate a random vector for upsert
    vector_to_upsert = [random.random() for _ in range(dimension)]
    # Additional metadata associated with the vector
    metadata = {"text": "example test for metadata"}

    # Upsert the vector into the index
    index.upsert(vectors=[
        ("id-for-vector", vector_to_upsert, metadata)
    ])

```
The example above demonstrates how to upsert a vector with metadata using the SDK into the Upstash Vector database.

## More SDK Features
For additional functionalities and usage examples, check out the [Commands](/docs/vector/sdks/py/example_calls) section in the documentation.

# Advanced
Source: https://upstash.com/docs/vector/sdks/ts/advanced

## Request Timeout

You can configure the SDK so that it will throw an error if the request takes longer than a specified time.

You can achieve this using the signal parameter like this:

```ts

const index = new Index({
  url: "<UPSTASH_VECTOR_REST_URL>",
  token: "<UPSTASH_VECTOR_REST_TOKEN>",
  // set a timeout of 1 second
  signal: () => AbortSignal.timeout(1000),
});

try {
  await index.query({ ... })
} catch (error) {
  if (error.name === "TimeoutError") {
    console.error("Request timed out");
  } else {
    console.error("An error occurred:", error);
  }
}
```

## Telemetry

This sdk sends anonymous telemetry data to help us improve your experience.
We collect the following:

* SDK version
* Platform (Cloudflare, AWS or Vercel)
* Runtime version (node@18.x)

You can opt out by setting the `UPSTASH_DISABLE_TELEMETRY` environment variable
to any truthy value.

```sh
UPSTASH_DISABLE_TELEMETRY=1
```

- [Delete](https://upstash.com/docs/vector/sdks/ts/commands/delete.md)
- [Fetch](https://upstash.com/docs/vector/sdks/ts/commands/fetch.md)
- [Info](https://upstash.com/docs/vector/sdks/ts/commands/info.md)
- [Query](https://upstash.com/docs/vector/sdks/ts/commands/query.md)
- [Range](https://upstash.com/docs/vector/sdks/ts/commands/range.md)
- [Reset](https://upstash.com/docs/vector/sdks/ts/commands/reset.md)
- [Resumable Query](https://upstash.com/docs/vector/sdks/ts/commands/resumable-query.md)
- [Upsert](https://upstash.com/docs/vector/sdks/ts/commands/upsert.md)

# Contributing
Source: https://upstash.com/docs/vector/sdks/ts/contributing

## Preparing the environment

This project uses [Bun](https://bun.sh/) for packaging and dependency management. Make sure you have the relevant dependencies.

```commandline
curl -fsSL https://bun.sh/install | bash
```

You will also need a vector database on [Upstash](https://console.upstash.com/).

## Code Formatting

```bash
bun run fmt
```

## Running tests

To run all the tests, make sure you have the relevant environment variables.

```bash
bun run test
```

# Getting Started
Source: https://upstash.com/docs/vector/sdks/ts/getting-started

`@upstash/vector` is a Typescript SDK for Upstash Vector, enabling easier operations on Vector Store with full type coverage.

Using `@upstash/vector` you can:

* Upsert a vector with metadata to an index.
* Fetching the vectors with specified IDs.
* Querying a vector over pre-defined embeddings.
* Delete vectors from an index.
* Access index stats.
* Reset everything related to an index.

You can find the Github Repository [here](https://github.com/upstash/vector-js).

## Install

<CodeGroup>
```shell npm
npm install @upstash/vector
```

```shell pnpm
pnpm add @upstash/vector
```

</CodeGroup>

## Usage

### Initializing the client

There are two pieces of configuration required to use the Upstash vector client: a REST token and REST URL. These values can be passed using environment variables or in code through a configuration object. Find your configuration values in the console dashboard at [https://console.upstash.com/](https://console.upstash.com/).

#### Using environment variables

The environment variables used to configure the client are the following. You can follow [this guide](/docs/vector/overall/getstarted) to retrieve credentials.

```bash
UPSTASH_VECTOR_REST_URL="your_rest_url"
UPSTASH_VECTOR_REST_TOKEN="your_rest_token"
```

When these environment variables are set, the client constructor does not require any additional arguments.

```typescript
import { Index } from "@upstash/vector";

const index = new Index();
```

#### Using a configuration object

If you prefer to pass configuration in code, the constructor accepts a config object containing the `url` and `token` values. This
could be useful if your application needs to interact with multiple projects, each with a different configuration.

```typescript
import { Index } from "@upstash/vector";

const index = new Index({
  url: "<UPSTASH_VECTOR_REST_URL>",
  token: "<UPSTASH_VECTOR_REST_TOKEN>",
});
```

## TypeScript Usage

### Index level types
The Vector SDK supports defining your metadata type at the index level for complete type-safety.

```typescript
import { Index } from "@upstash/vector";

type Metadata = { genre: string, year: number };

const index = new Index<Metadata>();
```

Passing a metadata type at the index level will provide strong type safety for the metadata coming back from or required for the following commands:
* `query`
* `upsert`
* `fetch`
* `range`

### Command level types

In some cases, you might not want to define a metadata type at the index level. In this case, you can either override the index level type definition for a specific command, or pass a metadata type to specific commands instead.

```typescript
import { Index } from "@upstash/vector";

type Metadata = { genre: string, year: number };

const index = new Index();

index.upsert<Metadata>({ id: 1, vector: [...], metadata: {
  genre: "comedy",
  year: 1990
}});
```

The passing of a strong metadata type is possible for each of the four index operations listed above, we use upsert as an example.

# Create and Deploy RAG Applications with Gradio
Source: https://upstash.com/docs/vector/tutorials/gradio-application

In this tutorial, we'll demonstrate how to use Gradio to build an interactive Semantic Search and Question Answering app using Hugging Face embeddings, Upstash Vector, and LangChain. Users can enter a question, and the app will retrieve relevant information and provide an answer.

### Important Note on Python Version

Recent Python versions may cause compatibility issues with `torch`, a dependency for Hugging Face models. Therefore, we recommend using **Python 3.9** to avoid any installation issues.

### Installation and Setup

First, we need to set up our environment and install the necessary libraries. Install the dependencies by running the following command:

```bash
pip install gradio langchain sentence_transformers upstash-vector python-dotenv transformers langchain-community langchain-huggingface
```

Next, create a `.env` file in your project directory with the following content, replacing `your_upstash_url` and `your_upstash_token` with your actual Upstash credentials:

```
UPSTASH_VECTOR_REST_URL=your_upstash_url
UPSTASH_VECTOR_REST_TOKEN=your_upstash_token
```

This configuration file will allow us to load the required environment variables.

### Code

We will load our environment variables, initialize the Hugging Face embeddings model, set up Upstash Vector, and configure a Hugging Face Question Answering model.

```python
# Import libraries
import gradio as gr
from dotenv import load_dotenv
from langchain_huggingface.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores.upstash import UpstashVectorStore
from transformers import pipeline
from langchain.schema import Document

# Load environment variables
load_dotenv()

# Set up embeddings and Upstash Vector store
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
vector_store = UpstashVectorStore(embedding=embeddings)
```

Next, we will create sample documents, embed them using Hugging Face embeddings, and store them in Upstash Vector.

```python
# Sample documents to embed and store
documents = [
    Document(page_content="Global warming is causing sea levels to rise."),
    Document(page_content="AI is transforming many industries."),
    Document(page_content="Renewable energy is vital for sustainable development.")
]
vector_store.add_documents(documents=documents, batch_size=100, embedding_chunk_size=200)
```

When inserting documents, they are first embedded using the `Embeddings` object. Many embedding models, such as the Hugging Face models, support embedding multiple documents at once. This allows for efficient processing by batching documents and embedding them in parallel.

* The `embedding_chunk_size` parameter controls the number of documents processed in parallel when creating embeddings.

Once the embeddings are created, they are stored in Upstash Vector. To reduce the number of HTTP requests, the vectors are also batched when they are sent to Upstash Vector.

* The `batch_size` parameter controls the number of vectors included in each HTTP request when sending to Upstash Vector.

<Note type="info">
In the Upstash Vector free tier, there is a limit of 1000 vectors per batch.
</Note>

Now, we can set up a Question Answering model and the Gradio interface.

```python
# Set up a Hugging Face Question Answering model
qa_pipeline = pipeline("question-answering", model="distilbert-base-cased-distilled-squad")

# Gradio interface function
def answer_question(query):
    # Retrieve relevant documents from Upstash Vector
    results = vector_store.similarity_search(query, k=3)

    # Use the most relevant document for QA
    if results:
        context = results[0].page_content
        qa_input = {"question": query, "context": context}
        answer = qa_pipeline(qa_input)["answer"]
        return f"Answer: {answer}\n\nContext: {context}"
    else:
        return "No relevant context found."

# Set up Gradio interface
iface = gr.Interface(
    fn=answer_question,
    inputs="text",
    outputs="text",
    title="RAG Application",
    description="Ask a question, and the app will retrieve relevant information and provide an answer."
)

# Launch the Gradio app
iface.launch()
```

### Running the App

After setting up the code, run your script to start the Gradio app. You will be presented with an interface where you can enter a question. The app will retrieve the most relevant information from the embedded documents and provide an answer based on the content.

### Notes

* **Deployment**: To create a public link, set `share=True` in `launch()`. This will generate a public URL for your Gradio app. This share link expires in 72 hours. For free permanent hosting and GPU upgrades, run `gradio deploy` from Terminal to deploy to [Hugging Face Spaces](https://huggingface.co/spaces)
* **Batch Processing**: The `batch_size` and `embedding_chunk_size` parameters allow you to control the efficiency of document processing and storage in Upstash Vector.
* **Namespaces**: Upstash Vector supports namespaces for organizing different types of documents. You can set a namespace while creating the `UpstashVectorStore` instance.

# Use Hugging Face Embeddings with Upstash Vector
Source: https://upstash.com/docs/vector/tutorials/huggingface-embeddings

In this tutorial, we'll demonstrate how to use Hugging Face embeddings with Upstash Vector and LangChain to perform a similarity search. We will upload a few sample documents, embed them using Hugging Face, and then perform a search query to find the most semantically similar documents.

### Important Note on Python Version

Recent Python versions may cause compatibility issues with `torch`, a dependency for Hugging Face models. Therefore, we recommend using **Python 3.9** to avoid any installation issues.

### Installation and Setup

First, we need to set up our environment and install the necessary libraries. Install the dependencies by running the following command:

```bash
pip install langchain sentence_transformers upstash-vector python-dotenv langchain-community langchain-huggingface
```

Next, create a `.env` file in your project directory with the following content, replacing `your_upstash_url` and `your_upstash_token` with your actual Upstash credentials:

```
UPSTASH_VECTOR_REST_URL=your_upstash_url
UPSTASH_VECTOR_REST_TOKEN=your_upstash_token
```

This configuration file will allow us to load the required environment variables.

### Code

We will load our environment variables and initialize the Hugging Face embeddings model along with the Upstash Vector store.

```python
# Load environment variables for API keys and Upstash configuration
from dotenv import load_dotenv
import os
load_dotenv()

# Import required libraries
from langchain_huggingface.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores.upstash import UpstashVectorStore

# Initialize Hugging Face embeddings model
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")

# Set up Upstash Vector Store (automatically uses the environment variables)
vector_store = UpstashVectorStore(embedding=embeddings)
```

Next, we will create sample documents and embed them using Hugging Face embeddings, then store them in Upstash Vector.

```python
# Import the required Document class from LangChain
from langchain.schema import Document

# Sample documents to embed and store as Document objects
documents = [
    Document(page_content="Global warming is causing sea levels to rise."),
    Document(page_content="Artificial intelligence is transforming many industries."),
    Document(page_content="Renewable energy is vital for sustainable development.")
]

# Embed documents and store in Upstash Vector with batching
vector_store.add_documents(
    documents=documents,
    batch_size=100,
    embedding_chunk_size=200
)

print("Documents with embeddings have been stored in Upstash Vector.")
```

When inserting documents, they are first embedded using the `Embeddings` object. Many embedding models, such as the Hugging Face models, support embedding multiple documents at once. This allows for efficient processing by batching documents and embedding them in parallel.

* The `embedding_chunk_size` parameter controls the number of documents processed in parallel when creating embeddings.

Once the embeddings are created, they are stored in Upstash Vector. To reduce the number of HTTP requests, the vectors are also batched when they are sent to Upstash Vector.

* The `batch_size` parameter controls the number of vectors included in each HTTP request when sending to Upstash Vector.

<Note type="info">
In the Upstash Vector free tier, there is a limit of 1000 vectors per batch.
</Note>

Now, we can perform a semantic search.

```python
# Querying Vectors using a text query
query_text = "What are the effects of global warming?"
query_embedding = embeddings.embed_query(query_text)

# Perform similarity search with the query text
result_text_query = vector_store.similarity_search(
    query=query_text,
    k=5  # Number of top results to return
)

print("Results for text-based similarity search:")
for res in result_text_query:
    print(res.page_content)
```

Here's the output of our text-based similarity search:

```
Results for text-based similarity search:
Global warming is causing sea levels to rise.
Renewable energy is vital for sustainable development.
Artificial intelligence is transforming many industries.
```

Alternatively, you can perform a similarity search using the vector directly.

```python
# Querying Vectors using a vector directly
result_vector_query = vector_store.similarity_search_by_vector(
    embedding=query_embedding,
    k=5
)

print("Results for vector-based similarity search:")
for res in result_vector_query:
    print(res.page_content)
```

This will output similar results, as it is searching based on the similarity of the embedding.

### Notes

* You can specify batch sizes and chunk sizes to control the efficiency of document processing and storage in Upstash Vector.
* Upstash Vector supports namespaces for organizing different types of documents. You can set a namespace while creating the `UpstashVectorStore` instance.

To learn more about LangChain and its integration with Upstash Vector, visit the [LangChain documentation](https://python.langchain.com/docs/integrations/vectorstores/upstash/).

# Implement Semantic Search with LangChain
Source: https://upstash.com/docs/vector/tutorials/langchain

In this tutorial, we'll demonstrate how to use Upstash Vector with LangChain to perform a similarity search. We will upload a document about global warming and perform a search query to find the most semantically similar documents using embeddings generated automatically by Upstash.

### Installation and Setup

First, we need to create a Vector Index in the [Upstash Console](https://console.upstash.com). Once we have our index, we will copy the `UPSTASH_VECTOR_REST_URL` and `UPSTASH_VECTOR_REST_TOKEN` and paste them to our `.env` file. To learn more about index creation, you can check out [this page](https://docs.upstash.com/vector/overall/getstarted).

Add the following content to your `.env` file (replace with your actual URL and token):

```
UPSTASH_VECTOR_REST_URL=your_upstash_url
UPSTASH_VECTOR_REST_TOKEN=your_upstash_token
```

We now need to install the following libraries via PyPI:

```bash
pip install upstash-vector python-dotenv langchain langchain-community
```

### Code

We will load our environment variables and initialize the index from the environment variables (URL and token).

```python
from dotenv import load_dotenv
from langchain_community.vectorstores.upstash import UpstashVectorStore

load_dotenv()

# Create a vector store instance where embeddings are generated by Upstash
store = UpstashVectorStore(embedding=True)
```

Next, we will upload the `global_warming.txt` document to the Upstash Vector index.

```python
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import CharacterTextSplitter

# Load the document
loader = TextLoader("documents/global_warming.txt")
documents = loader.load()

# Split the document into chunks
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(documents)
```

We will now insert the documents into the Upstash Vector index.

```python
inserted_vectors = store.add_documents(docs)
```

Finally, we will perform a semantic search.

```python
result = store.similarity_search("Technology's role in global warming.", k=5)
print(result)
```

Here's the output:
```
[Document(metadata={'source': 'documents/global_warming.txt'}, page_content='Technology and innovation play a crucial role in advancing sustainable food systems and mitigating the impact of global warming. Precision agriculture, for example, uses data analytics, remote sensing, and GPS technology to optimize the use of resources such as water, fertilizer, and pesticides. By applying inputs more efficiently, precision agriculture reduces waste, lowers GHG emissions, and improves crop yields.\n\nIn the realm of food production, alternative proteins—such as plant-based meats, lab-grown meats, and insect-based proteins—offer promising solutions to reduce the environmental impact of livestock farming. These innovations require fewer resources to produce and generate lower GHG emissions compared to traditional animal agriculture. Additionally, vertical farming and hydroponics allow for the cultivation of crops in controlled environments, using less land and water while reducing the need for chemical inputs.\n\nConclusion'),
 Document(metadata={'source': 'documents/global_warming.txt'}, page_content='Global warming presents a formidable challenge to food production systems, but sustainable food practices offer a viable solution to mitigate its effects. By adopting regenerative agriculture, reducing food waste, and shifting toward plant-based diets, humanity can reduce the environmental impact of agriculture while ensuring food security for future generations. Furthermore, technology and innovation will continue to play a pivotal role in advancing sustainable food systems, allowing for more efficient and eco-friendly food production methods. In the face of climate change, the integration of sustainable food practices is not only an environmental imperative but also a pathway toward a healthier and more resilient future for both people and the planet.'),
 Document(metadata={'source': 'documents/global_warming.txt'}, page_content='Global Warming and Sustainable Foods: An Inextricable Connection'),
 Document(metadata={'source': 'documents/global_warming.txt'}, page_content='One of the key components of sustainable food systems is regenerative agriculture, which focuses on restoring soil health, enhancing biodiversity, and sequestering carbon. Regenerative farming practices include crop rotation, cover cropping, agroforestry, and reduced tillage, all of which contribute to building healthy soils that can absorb and store carbon. By improving soil health, regenerative agriculture also increases the resilience of crops to climate change, as healthy soils retain more water and nutrients, making crops more resistant to droughts and pests.'),
 Document(metadata={'source': 'documents/global_warming.txt'}, page_content='Numerous studies have shown that shifting to plant-based diets can significantly reduce GHG emissions and mitigate the effects of global warming. According to a report by the United Nations Food and Agriculture Organization (FAO), reducing the consumption of animal-based foods could reduce global agricultural emissions by up to 50%. Moreover, plant-based diets are associated with reduced land and water use, as growing crops for direct human consumption is more resource-efficient than growing feed for livestock.\n\nIn addition to their environmental benefits, plant-based diets offer numerous health advantages. They are rich in fiber, vitamins, and minerals, and have been linked to lower risks of chronic diseases such as heart disease, diabetes, and certain cancers. As global populations grow and the demand for food increases, promoting plant-based diets offers a sustainable solution to addressing both environmental and nutritional challenges.\n\nThe Role of Technology and Innovation')]
```

### Notes

* You can also query with score using `similarity_search_with_score` method.

* Namespaces can be used to separate different types of documents. You can specify a namespace when creating the `UpstashVectorStore` instance.
```
store = UpstashVectorStore(embedding=True, namespace="my_namespace")
```

* You can use OpenAI's embeddings by setting `embedding=OpenAIEmbeddings()` in the `UpstashVectorStore` instance.
```
from langchain_openai import OpenAIEmbeddings
store = UpstashVectorStore(embedding=OpenAIEmbeddings())
```

To learn more about LangChain and its integration with Upstash Vector, you can visit the [LangChain documentation](https://python.langchain.com/docs/integrations/vectorstores/upstash/).

# RAG with LlamaIndex
Source: https://upstash.com/docs/vector/tutorials/llamaindex

In this tutorial, we’ll demonstrate how to use Upstash Vector with LlamaIndex to perform RAG (Retrieval-Augmented Generation). We will upload a document about global warming and generate responses to our questions based on the contents of the document.

### Installation and Setup

First, we need to create a Vector Index in the [Upstash Console](https://console.upstash.com). Make sure to set the index dimensions to 1536 and the distance metric to Cosine. Once we have our index, we will copy the `UPSTASH_VECTOR_REST_URL` and `UPSTASH_VECTOR_REST_TOKEN` and paste them into our `.env` file. To learn more about index creation, you can check out our [getting started page](https://docs.upstash.com/vector/overall/getstarted).

Add the following content to your `.env` file (replace with your actual URL, token and API key):

```
UPSTASH_VECTOR_REST_URL=your_upstash_url
UPSTASH_VECTOR_REST_TOKEN=your_upstash_token
OPENAI_API_KEY=your_openai_api_key
```

We now need to install the following libraries via PyPI:

```bash
pip install llama-index upstash-vector llama-index-vector-stores-upstash python-dotenv
```

### Code

We will load our environment variables, initialize the index, and configure it to use the specified dimensions and distance metric.

```python
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.vector_stores.upstash import UpstashVectorStore
from llama_index.core import StorageContext
import openai
import os
from dotenv import load_dotenv

# Load environment variables
load_dotenv()
openai.api_key = os.environ["OPENAI_API_KEY"]

# Setup the Upstash vector store
upstash_vector_store = UpstashVectorStore(
    url=os.environ["UPSTASH_VECTOR_REST_URL"],
    token=os.environ["UPSTASH_VECTOR_REST_TOKEN"],
)

# Read the document about global warming from the documents directory
documents = SimpleDirectoryReader("./documents/").load_data()

# Initialize the storage context with the Upstash vector store
storage_context = StorageContext.from_defaults(vector_store=upstash_vector_store)

# Create the index from the loaded document with 1536 dimensions and cosine distance
index = VectorStoreIndex.from_documents(
    documents, storage_context=storage_context
)
```

Next, we will query the document:

```python
# Initialize the query engine
query_engine = index.as_query_engine()

# Query the document about global warming
res1 = query_engine.query("What is global warming?")
print(res1)

res2 = query_engine.query("How should we modify our diets to reduce our carbon footprint?")
print(res2)
```

### Sample Output

Here is the output of the queries:

```
Global warming refers to the long-term increase in Earth’s average temperature due to the accumulation of greenhouse gases (GHGs) such as carbon dioxide (CO2), methane (CH4), and nitrous oxide (N2O) in the atmosphere. The primary drivers of GHG emissions include the burning of fossil fuels for energy, industrial processes, deforestation, and unsustainable agricultural practices. As these gases trap heat, they create a “greenhouse effect,” leading to rising temperatures, melting polar ice caps, rising sea...

Shifting towards plant-based diets, which emphasize the consumption of vegetables, fruits, legumes, grains, and nuts, can significantly reduce our carbon footprint. Plant-based diets have a much lower environmental footprint compared to animal-based foods, particularly red meat, which is highly resource-intensive and contributes disproportionately to greenhouse gas emissions. Studies have shown that reducing the consumption of animal-based foods and increasing the intake of plant-based foods can help red...
```

### Notes

* Namespaces can be used to separate different types of documents. You can specify a namespace when creating the `UpstashVectorStore` instance:
```
vector_store = UpstashVectorStore(
    url=your_upstash_url,
    token=your_upstash_token,
    namespace=your_namespace,
    )
```

* To learn more about LlamaIndex and its integration with Upstash Vector, you can visit the [LlamaIndex documentation](https://docs.llamaindex.ai/en/latest).

# Parsing and Querying Documents with LlamaParse
Source: https://upstash.com/docs/vector/tutorials/llamaparse

In this tutorial, we’ll learn how to parse a document using LlamaParse and then query it using an LLM with Upstash Vector.

We’ll split this guide into two parts: parsing a document and then querying the parsed document.

## Installation and Setup

To get started, we need to set up our environment. You can install the necessary libraries using the following command in your terminal:

```bash
pip install llama-index upstash-vector llama-index-vector-stores-upstash python-dotenv
```

We also need to create a Vector Index in the [Upstash Console](https://console.upstash.com). Make sure to set the index dimensions to 1536 and the distance metric to Cosine. To learn more about index creation, you can check out our [getting started page](https://docs.upstash.com/vector/overall/getstarted).

Once we have our index, we will copy the `UPSTASH_VECTOR_REST_URL` and `UPSTASH_VECTOR_REST_TOKEN` and paste them into our `.env` file.

#### Environment Variables

Create a `.env` file in your project directory and add the following content:

```plaintext
UPSTASH_VECTOR_REST_URL=your_upstash_url
UPSTASH_VECTOR_REST_TOKEN=your_upstash_token
OPENAI_API_KEY=your_openai_api_key
LLAMA_CLOUD_API_KEY=your_llama_cloud_api_key
```

To get your `LLAMA_CLOUD_API_KEY`, you can follow the instructions in [the LlamaCloud documentation](https://docs.cloud.llamaindex.ai/llamaparse/getting_started/get_an_api_key).

## Part 1: Parsing a Document

We can now move on to parsing a document. In this example, we’ll parse a file named `global_warming.txt`.

```python
from llama_parse import LlamaParse
from llama_index.core import SimpleDirectoryReader

# Initialize the LlamaParse parser with the desired result format
parser = LlamaParse(result_type="markdown")  # "markdown" and "text" are available

# Parse the document using the parser
file_extractor = {".txt": parser}
documents = SimpleDirectoryReader(input_files=["./documents/global_warming.txt"], file_extractor=file_extractor).load_data()
```

<Note type="info">
    If you are using Jupyter Notebook, you need to allow nested event loops to parse the document.

    You can do this by adding the following code snippet to your file:
```python
import nest_asyncio
nest_asyncio.apply()
```
</Note>

Now that we have our parsed data, we can query it.

## Part 2: Querying the Parsed Document with an LLM

In this part, we’ll use the `UpstashVectorStore` to create an index, and query the content. We’ll use OpenAI as the language model to interpret the data and respond to questions based on the document. You can use other LLMs that are supported by LlamaIndex as well.

```python
from llama_index.core import VectorStoreIndex
from llama_index.vector_stores.upstash import UpstashVectorStore
from llama_index.core import StorageContext
import openai

# Load environment variables for API keys and Upstash configuration
from dotenv import load_dotenv
import os
load_dotenv()

# Set up OpenAI API key
openai.api_key = os.getenv("OPENAI_API_KEY")

# Set up Upstash Vector Store
upstash_vector_store = UpstashVectorStore(
    url=os.getenv("UPSTASH_VECTOR_REST_URL"),
    token=os.getenv("UPSTASH_VECTOR_REST_TOKEN"),
)

# Create a storage context for Upstash Vector and index the parsed document
storage_context = StorageContext.from_defaults(vector_store=upstash_vector_store)
index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)

# Create a query engine for the index and perform a query
query_engine = index.as_query_engine()
query = "What are the main points discussed in the document?"
response = query_engine.query(query)
print(response)
```

Here's the code output:

```plaintext
The main points discussed in the document include the impact of global warming on agriculture
and food production systems, the importance of adopting sustainable food practices to mitigate
these effects, the role of agriculture in contributing to global warming through GHG emissions,
deforestation, and the use of synthetic fertilizers, and the need for sustainable food systems
to address environmental challenges and ensure food security for future generations.
```

### Conclusion

With the ability to parse and query documents, you can efficiently summarize content, extract essential information, and answer questions based on the document’s details.

To learn more about LlamaIndex and its integration with Upstash Vector, you can visit the [LlamaIndex documentation](https://docs.llamaindex.ai/en/latest).

# Simple Semantic Search
Source: https://upstash.com/docs/vector/tutorials/semantic_search

In this tutorial, we'll demonstrate how to use Upstash Vector for semantic search. We will upload several documents and perform a search query to find the most semantically similar documents using embeddings generated automatically by Upstash.

### Installation and Setup

First, we need to create a Vector Index in the [Upstash Console](https://console.upstash.com). Once we have our index, we will copy the `UPSTASH_VECTOR_REST_URL` and `UPSTASH_VECTOR_REST_TOKEN` and paste them to our `.env` file. To learn more about index creation, you can check out [this page](https://docs.upstash.com/vector/overall/getstarted).

Add the following content to your `.env` file (replace with your actual URL and token):

```
UPSTASH_VECTOR_REST_URL=your_upstash_url
UPSTASH_VECTOR_REST_TOKEN=your_upstash_token
```

We now need to install the `upstash-vector` library via PyPI. Additionally, we will install `python-dotenv` to load environment variables from the `.env` file.

```bash
pip install upstash-vector python-dotenv
```

### Code

Create a Python script (e.g., `main.py`) and add the following code to perform semantic search using Upstash Vector:

```python main.py
from upstash_vector import Index
from dotenv import load_dotenv
import time

# Load environment variables from a .env file
load_dotenv()

# Initialize the index from environment variables (URL and token)
index = Index.from_env()

# Example documents to be indexed
documents = [
    {"id": "1", "text": "Python is a popular programming language."},
    {"id": "2", "text": "Machine learning enables computers to learn from data."},
    {"id": "3", "text": "Upstash provides low-latency database solutions."},
    {"id": "4", "text": "Semantic search is a technique for understanding the meaning of queries."},
    {"id": "5", "text": "Cloud computing allows for scalable and flexible resource management."}
]

# Reset the index to remove previous data
index.reset()

# Upsert documents into Upstash (embeddings are generated automatically)
for doc in documents:
    index.upsert(
        vectors=[
            (doc["id"], doc["text"], {"text": doc["text"]})
        ]
    )
    print(f"Document {doc['id']} inserted.")

# Wait for the documents to be indexed
time.sleep(1)

# Search for documents similar to the query
query = "What is Python?"
results = index.query(data=query, top_k=3, include_metadata=True)

# Display search results
print("Search Results:")
for result in results:
    print(f"ID: {result.id}")
    print(f"Score: {result.score:.4f}")
    print(f"Metadata: {result.metadata}")
    print("-" * 40)  # Separator line between results
```

### Running the Code

To run the code, execute the following command in your terminal:

```bash
python main.py
```

Here is an example output for the search query "What is Python?":

```
Document 1 inserted.
Document 2 inserted.
Document 3 inserted.
Document 4 inserted.
Document 5 inserted.
Search Results:
ID: 1
Score: 0.9080
Metadata: {'text': 'Python is a popular programming language.'}
----------------------------------------
ID: 2
Score: 0.7592
Metadata: {'text': 'Machine learning enables computers to learn from data.'}
----------------------------------------
ID: 4
Score: 0.7388
Metadata: {'text': 'Semantic search is a technique for understanding the meaning of queries.'}
----------------------------------------
```

### Code Breakdown

1. **Environment Setup**: We use `python-dotenv` to load our environment variables and use the `Index.from_env()` method to initialize the index client.

2. **Document Insertion**: We define a list of documents, each with a unique ID and text content. The `upsert()` function inserts these documents into our index. These documents are automatically converted into embeddings. To learn more about Upstash Embedding Models, you can check out [this page](https://docs.upstash.com/vector/features/embeddingmodels).

3. **Index Reset**: Before inserting documents, the `reset()` function clears any existing data in the index.

4. **Search Query**: After inserting the documents, we perform semantic search. The `query()` function returns the `top_k` most similar documents to the query along with their metadata if `include_metadata` is set to `True`.

# Examples
Source: https://upstash.com/docs/workflow/agents/examples

Explore our collection of examples to learn how to build robust agent systems. From architectural patterns to real-world implementations, these examples demonstrate the full potential of our Agents API.

## Real World Examples

Practical implementations demonstrating how to use our Agents API in production scenarios. These examples provide ready-to-use templates for common use cases.

<CardGroup cols={2}>
  <Card
    title="Browser Automation"
    icon="globe"
    href="https://github.com/upstash/workflow-js/blob/main/examples/agents/app/agentic-browser-search/route.ts"
  >
    Autonomous web navigation and interaction system for content extraction and
    form handling.
  </Card>
  <Card
    title="Email Analyzer"
    icon="envelope"
    href="https://github.com/upstash/workflow-js/blob/main/examples/agents/app/email-analyzer/route.ts"
  >
    Intelligent email processing system for content analysis, classification,
    and automated responses.
  </Card>
  <Card
    title="Social Media Manager"
    icon="share-nodes"
    href="https://github.com/upstash/workflow-js/blob/main/examples/agents-instagram-post-generator"
  >
    Multi-platform social media management system for content moderation and
    engagement automation.
  </Card>
</CardGroup>

# Features
Source: https://upstash.com/docs/workflow/agents/features

On this page, we explain the features of the Workflow Agents API in more detail.

Prerequisites:
* Setup your first Agent endpoint by following [the Getting Started page](/docs/workflow/agents/getting-started).
* Install the following packages to define tools:

```bash
npm i ai mathjs zod @agentic/ai-sdk @agentic/weather @langchain/core @langchain/community
```

## Models

The `model` is responsible for deciding which tools to call and generating the final response

First, add `QSTASH_TOKEN` and `OPENAI_API_KEY` to your environment:

```
QSTASH_TOKEN="<QSTASH_TOKEN>"
OPENAI_API_KEY="<OPENAI_API_KEY>"
```

Next, define the model in your route:

```ts
import { serve } from "@upstash/workflow/nextjs";
import { agentWorkflow } from "@upstash/workflow-agents";

export const { POST } = serve(async (context) => {
  const agents = agentWorkflow(context);
  const model = agents.openai('gpt-3.5-turbo')

  // ...
})
```

If you want to use an OpenAI compatible provider, you can do so by passing `baseURL` and `apiKey`:

```ts
const model = agents.openai('deepseek-chat', {
  baseURL: "https://api.deepseek.com",
  apiKey: process.env.DEEPSEEK_API_KEY
})
```

If you want to use other providers available on AI SDK, you can import the `create<Provider>` methods exported from provider packages. For example, in order to use Anthropic you can do the following:

```ts
import { createAnthropic } from "@ai-sdk/anthropic";

const model = agents.AISDKModel({
  context,
  provider: createAnthropic,
  providerParams: {
    apiKey: "<ANTHROPIC_API_KEY>",
  },
});
```

If you want to configure the calls made to the LLM APIs, you can use the `agentCallParams` parameter:

```ts
const model = agents.openai('gpt-3.5-turbo', {
  callSettings: {
    timeout: 1000, // optional request timeout
    retries: 0,    // optional retries
    flowControl: { // optional flow control
      key: "flow-control-key",
      rate: 10,
      period: "10s",
      parallelism: 10,
    },
  }
})
```

Same parameter is available in `agents.AISDKModel` as the `agentCallParams` parameter.

## Tools

Next, we will define the tools that our agents will use. The **Agents API** is compatible with both **AI SDK** and **LangChain** tools. This means you can either:

* Use existing tools that are compatible with these SDKs.
* Define your own custom tools that are compatible with these SDKs.

This flexibility allows you to tailor the tools to your specific needs while leveraging the power of these frameworks.

<CodeGroup>

```ts WorkflowTool (custom)
import { WorkflowTool } from '@upstash/workflow'
import { z } from 'zod'
import * as mathjs from 'mathjs'

const tool = new WorkflowTool({
  description:
    'A tool for evaluating mathematical expressions. ' +
    'Example expressions: ' +
    "'1.2 * (2 + 4.5)', '12.7 cm to inch', 'sin(45 deg) ^ 2'.",
  schema: z.object({ expression: z.string() }),
  invoke: async ({ expression }) => mathjs.evaluate(expression),
})
```

```ts AI SDK (custom)
import { z } from 'zod'
import { tool } from 'ai'
import * as mathjs from 'mathjs'

const mathTool = tool({
  description:
    'A tool for evaluating mathematical expressions. ' +
    'Example expressions: ' +
    "'1.2 * (2 + 4.5)', '12.7 cm to inch', 'sin(45 deg) ^ 2'.",
  parameters: z.object({ expression: z.string() }),
  execute: async ({ expression }) => mathjs.evaluate(expression),
})
```

```ts Agentic
import { createAISDKTools } from '@agentic/ai-sdk'
import { WeatherClient } from '@agentic/weather'

const weather = new WeatherClient()
const tools = createAISDKTools(weather)
```

```ts LangChain (custom)
import { DynamicStructuredTool } from "@langchain/core/tools";

const numberGenerator = new DynamicStructuredTool({
  name: "random-number-generator",
  description: "generates a random number between two input numbers",
  schema: z.object({
    low: z.number().describe("The lower bound of the generated number"),
    high: z.number().describe("The upper bound of the generated number"),
  }),
  func: async ({ low, high }) =>
    (Math.random() * (high - low) + low).toString(), // Outputs still must be strings
})
```

```ts LangChain
import { WikipediaQueryRun } from '@langchain/community/tools/wikipedia_query_run'

const wikiTool = new WikipediaQueryRun({
  topKResults: 1,
  maxDocContentLength: 500,
})
```

</CodeGroup>

For available toolkits, you can explore the [LangChain Toolkits](https://js.langchain.com/v0.1/docs/modules/agents/tools/toolkits/) or other AI SDK-compatible toolkits like [Agentic](https://agentic.so/sdks/ai-sdk).

By default, the Workflow SDK will wrap the execute/invoke methods of the tools you pass with [`context.run`](/docs/workflow/basics/context#context-run) to run them as a Workflow step. This means, you can't use steps like context.call, context.notify etc in execute/invoke right away. If you want to define steps in invoke/execute, you can use the `executeAsStep` option:

```ts {12}
import { WorkflowTool } from '@upstash/workflow'
import { serve } from '@upstash/workflow/nextjs'

export const { POST } = serve(async (context) => {
  const tool = new WorkflowTool({
    description: ...,
    schema: ...,
    invoke: ( ... ) => {
      // make HTTP call inside the tool with context.call:
      await context.call( ... )
    },
    executeAsStep: false
  })

  // pass the tool to agent
})
```

## Agents

After defining a model and tools, we can define agents. Agents are essentially LLM models with access to a set of tools and background knowledge. In Upstash Workflow, agents are defined like this:

```ts route.ts
import { serve } from "@upstash/workflow/nextjs";
import { agentWorkflow } from "@upstash/workflow-agents";
import { WikipediaQueryRun } from "@langchain/community/tools/wikipedia_query_run";

export const { POST } = serve(async (context) => {
  const agents = agentWorkflow(context);
  const model = agents.openai('gpt-3.5-turbo')

  const researcherAgent = agents.agent({
    model,
    name: 'academic',
    maxSteps: 2,
    tools: {
      wikiTool: new WikipediaQueryRun({
        topKResults: 1,
        maxDocContentLength: 500,
      })
    },
    background:
      'You are researcher agent with access to Wikipedia. ' +
      'Utilize Wikipedia as much as possible for correct information',
  })
})
```

The parameters for defining an agent are as follows:

* **`model`**: The LLM model that the agent will use.
* **`name`**: The name of the agent. This name will be used when naming the [context.call steps](/docs/workflow/basics/context#context-call) for this agent. `context.call` is used when calling the LLM provider (such as OpenAI).
* **`maxSteps`**: The maximum number of times this agent can call the LLM provider (e.g., OpenAI).
* **`tools`**: The list of tools available to the agent for completing tasks.
* **`background`**: A description of the agent, which is used as a system prompt to provide context for the agent's behavior.

## Tasks

Now that we have agents defined, the only thing left is to assign tasks to them. A **task** is simply a prompt passed to the agent.

There are two ways to create a task:

1. **Single Agent Task**: The task is assigned to a single agent, which will complete it using the tools available to it.

2. **Multiple Agent Task**: A **manager agent** is used to decide which agents will be involved and in what order. The manager agent makes this decision based on the task prompt, the agents' backgrounds, and the tools available to them.

### Single Agent

```ts Single Agent {22-27}
import { serve } from "@upstash/workflow/nextjs";
import { agentWorkflow } from "@upstash/workflow-agents";
import { WikipediaQueryRun } from "@langchain/community/tools/wikipedia_query_run";

export const { POST } = serve(async (context) => {
  const agents = agentWorkflow(context);
  const model = agents.openai('gpt-3.5-turbo');

  const researcherAgent = agents.agent({
    model,
    name: 'academic',
    maxSteps: 2,
    tools: {
      wikiTool: new WikipediaQueryRun({
        topKResults: 1,
        maxDocContentLength: 500,
      })
    },
    background:
      'You are researcher agent with access to Wikipedia. ' +
      'Utilize Wikipedia as much as possible for correct information',
  });

  const task = agents.task({
    agent: researcherAgent,
    prompt: "Tell me about 5 topics in advanced physics.",
  });
  const { text } = await task.run();
  console.log("result:", text)
})
```

As response to the task, the agent generates the following response:

```
Here are summaries of 5 topics in advanced physics:

1. **Quantum Mechanics**: Quantum mechanics is a fundamental theory that describes the behavior of nature at and below the scale of atoms. It is the foundation of all quantum physics, including quantum chemistry, quantum field theory, quantum technology, and quantum information science. Quantum mechanics can describe many systems that classical physics cannot.

2. **General Relativity**: General relativity, also known as Einstein's theory of gravity, is the geometric theory of gravitation published by Albert Einstein in 1915. It is the current description of gravitation in modern physics, generalizing special relativity and refining Newton's law of universal gravitation. General relativity provides a unified description of gravity as a geometric property of space and time.

3. **Particle Physics**: Particle physics, also known as high-energy physics, is the study of fundamental particles and forces that constitute matter and radiation. The field also studies combinations of elementary particles up to the scale of protons and neutrons. Fundamental particles in the universe are classified in the Standard Model as fermions (matter particles) and bosons (force-carrying particles).

4. **Astrophysics**: Astrophysics is a science that applies the methods and principles of physics and chemistry to the study of astronomical objects and phenomena. It seeks to ascertain the nature of heavenly bodies and is distinct from celestial mechanics, which focuses on the positions and motions of objects in space. Subjects studied in astrophysics include the Sun, stars, galaxies, and the universe.

5. **String Theory**: String theory is a theoretical framework in physics where point-like particles are replaced by one-dimensional objects called strings. These strings describe how they propagate through space and interact with each other. On distance scales larger than the string scale, a string behaves like a particle, with its properties determined by the vibrational state of the string
```

Here is the logs on Upstash Console:

<img />

In the logs, you can see that the **academic agent** was called. It decided to invoke **wikiTool** five times in parallel. Once the tool requests were completed, the agent summarized the results from the individual calls in one final response and returned the outcome.

### Multi Agents

```ts Multi Agents {45-53}
import { serve } from "@upstash/workflow/nextjs";
import { agentWorkflow } from "@upstash/workflow-agents";
import { WikipediaQueryRun } from "@langchain/community/tools/wikipedia_query_run";
import * as mathjs from 'mathjs'
import { tool } from "ai";
import { z } from "zod";

export const { POST } = serve(async (context) => {
  const agents = agentWorkflow(context);
  const model = agents.openai('gpt-4o');

  const researcherAgent = agents.agent({
    model,
    name: 'academic',
    maxSteps: 2,
    tools: {
      wikiTool: new WikipediaQueryRun({
        topKResults: 1,
        maxDocContentLength: 500,
      })
    },
    background:
      'You are researcher agent with access to Wikipedia. ' +
      'Utilize Wikipedia as much as possible for correct information',
  });

  const mathAgent = agents.agent({
    model,
    name: "mathematician",
    maxSteps: 2,
    tools: {
      calculate: tool({
        description:
          'A tool for evaluating mathematical expressions. ' +
          'Example expressions: ' +
          "'1.2 * (2 + 4.5)', '12.7 cm to inch', 'sin(45 deg) ^ 2'." +
          "only call this tool if you need to calculate a mathematical expression." +
          "when writing an expression, don't use words like 'thousand' or 'million'",
        parameters: z.object({ expression: z.string() }),
        execute: async ({ expression }) => mathjs.evaluate(expression),
      }),
    },
    background:
      "You are a mathematician agent which can utilize" +
      "a calculator to compute expressions"
  })

  const task = agents.task({
    model,
    maxSteps: 3,
    agents: [researcherAgent, mathAgent],
    prompt: "Tell me about 3 cities in Japan and calculate the sum of their populations",
  });
  const { text } = await task.run();
  console.log("result:", text)
})
```

As response to the task, the agents generate the following response:

```
Here is a brief overview of three cities in Japan:

### Tokyo
- **Official Name**: Tokyo Metropolis
- **Population**: Over 14 million in the city proper as of 2023.
- **Significance**: Capital of Japan and one of the most populous urban areas in the world.

### Osaka
- **Japanese Name**: å¤§éå¸ (Åsaka-shi)
- **Population**: 2.7 million as per the 2020 census.
- **Significance**: Capital and most populous city in Osaka Prefecture, third-most populous city in Japan.

### Kyoto
- **Japanese Name**: äº¬é½å¸ (KyÅto-shi)
- **Population**: 1.46 million as of 2020.
- **Significance**: Capital city of Kyoto Prefecture, ninth-most populous city in Japan.

The sum of their populations is approximately 18.16 million.
```

Here are the logs on Upstash Console:

<img />

The logs show that the `Manager LLM` first called the `academic` agent. The `academic` agent used the `wikiTool` three times, each with a different Japanese city, and summarized the results. Next, the `Manager LLM` called the `mathematician` agent, which used its `calculate` tool to compute the total population of the three cities and returned the result to the `Manager LLM`. With information about the cities and their total population, the `Manager LLM` generated the final response.

# Getting Started
Source: https://upstash.com/docs/workflow/agents/getting-started

In this guide, we will be using **Next.js**. If you're working with a different supported framework, you can find instructions on how to define a workflow endpoint in the [quickstarts](/docs/workflow/quickstarts/platforms).

If you're new to **Upstash Workflow**, it's a good idea to start by exploring the [Local Development documentation](/docs/workflow/howto/local-development/development-server). This guide will help you set up and use Upstash Workflow in a local environment.

##  Installation

First, create a new Next.js project with:

```
npx create-next-app@latest [project-name] [options]
```

Then, install the following packages:

```
npm i @upstash/workflow @upstash/workflow-agents ai zod
```

## Start the development server

Since you are using `@upstash/workflow`, set `QSTASH_DEV=true` in your `.env.local` file and the SDK will download, start, and connect to a local QStash development server automatically — no tokens or signing keys to copy over. Add your `OPENAI_API_KEY` alongside it:

```txt .env.local
QSTASH_DEV=true

OPENAI_API_KEY=<OPENAI_API_KEY>
```

For manual setup, custom ports, and the `registerQStashDev()` helper for Next.js edge routes, see the [Development Server guide](/docs/workflow/howto/local-development/development-server).

## Define an endpoint

Next, we will define the endpoint to run the agent.

```ts app/workflow/route.ts
import { z } from "zod";
import { tool } from "ai";

import { serve } from "@upstash/workflow/nextjs";
import { agentWorkflow } from "@upstash/workflow-agents";

export const { POST } = serve<{ prompt: string }>(async (context) => {
  const prompt = context.requestPayload.prompt
  const agents = agentWorkflow(context)
  const model = agents.openai('gpt-3.5-turbo')

  const communicatorAgent = agents.agent({
    model,
    name: 'communicatorAgent',
    maxSteps: 2,
    tools: {
      communicationTool: tool({
        description: 'A tool for informing the caller about your inner thoughts',
        parameters: z.object({ message: z.string() }),
        execute: async ({ message }) => {
          console.log("Inner thought:", message)
          return "success"
        }
      })
    },
    background:
      'Answer questions directed towards you.' +
      ' You have access to a tool to share your inner thoughts' +
      ' with the caller. Utilize this tool at least once before' +
      ' answering the prompt. In your inner thougts, briefly' +
      ' explain what you will talk about and why. Keep your' +
      ' answers brief.',
  })

  const task = agents.task({
    agent: communicatorAgent,
    prompt
  })

  const { text } = await task.run()

  console.log("Final response:", text);
})
```

You can refer to the documentations for [defining a workflow endpoint](/docs/workflow/basics/serve) and [the Agents API features](/docs/workflow/agents/features) to learn more.

## Calling the Endpoint

To run the endpoint, first run the Next.js app with:

```bash
npm run dev
```

Then, we call the endpoint using [the Workflow Client](/docs/workflow/basics/client):

```ts
import { Client } from "@upstash/workflow";

const client = new Client({
  baseUrl: process.env.QSTASH_URL,
  token: process.env.QSTASH_TOKEN!,
})

const workflowRunId = await client.trigger({
  url: "http://127.0.0.1:3000/workflow",
  body: { prompt: "Explain the future of space exploration" }
})

console.log(workflowRunId);
```

<Tip>
  If you are using a local tunnel, replace the url above (`http://127.0.0.1:3000`)
  with the public URL.
</Tip>

In the console where you run the Next.js app, you should see logs like this:

```
Inner thought: I will discuss the future of space
exploration and the potential advancements in
technology and missions.

Final response: The future of space exploration
holds exciting possibilities with advancements
in technology, potential manned missions to
Mars, increased commercial space travel,
and exploration of distant celestial
bodies.
```

If you [run the same endpoint using a local tunnel](/docs/workflow/howto/local-development/local-tunnel), you can also see how Upstash Workflow runs the agent in steps:

<img />

Each tool invocation and LLM call is a seperate step. Our agent first made a call to OpenAI to decide whether to use a tool or reply right away. OpenAI responded with a request to use the tool `communicationTool`. Tool was executed and OpenAI was called with the result of the tool. OpenAI then responded with the final response.

# Overview
Source: https://upstash.com/docs/workflow/agents/overview

The **Agents API** of Upstash Workflow enables you to:
* Execute an individual agent or facilitate collaboration among multiple agents.
* Integrate any tool compatible with AI SDK or LangChain.
* Reliably invoke agents without concerns about timeouts or transient errors.
* Unlike mainstream agent frameworks, we prioritize debuggability and extensibility.

To get started, you can refer to the [Getting Started page](/docs/workflow/agents/getting-started). For more details about the features, you can refer to [the Features page](/docs/workflow/agents/features).

<Note>
  This feature is not yet available in
  [workflow-py](https://github.com/upstash/workflow-py). See our
  [Roadmap](/docs/workflow/roadmap) for feature parity plans and
  [Changelog](/docs/workflow/changelog) for updates.
</Note>

## Agent Patterns

If you're interested, you can also explore our rich examples that showcase how various patterns can be built using the Agents API:

<CardGroup cols={2}>
  <Card title="Prompt Chaining" icon="link" href="/workflow/agents/patterns/prompt-chaining">
    Sequential LLM calls where each output becomes the input for the next, enabling structured reasoning and step-by-step task completion.
  </Card>
  <Card title="Evaluator-optimizer" icon="arrows-rotate" href="/workflow/agents/patterns/evaluator-optimizer">
    A feedback loop where LLM outputs are evaluated and refined iteratively to improve accuracy and relevance.
  </Card>
  <Card title="Parallelization" icon="arrows-to-dot" href="/workflow/agents/patterns/parallelization">
    Distribute tasks across multiple LLMs and aggregate the results for efficient handling of complex or large-scale operations.
  </Card>
  <Card title="Orchestrator-workers" icon="sparkles" href="/workflow/agents/patterns/orchestrator-workers">
    A central orchestrator directs multiple worker LLMs to complete subtasks and synthesize their outputs for complex operations.
  </Card>
</CardGroup>

## Real World Examples

Practical implementations demonstrating how to use our Agents API in production scenarios. These examples provide ready-to-use templates for common use cases.

<CardGroup cols={2}>
  <Card
    title="Browser Automation"
    icon="globe"
    href="https://github.com/upstash/workflow-js/blob/main/examples/agents/app/agentic-browser-search/route.ts"
  >
    Autonomous web navigation and interaction system for content extraction and
    form handling.
  </Card>
  <Card
    title="Email Analyzer"
    icon="envelope"
    href="https://github.com/upstash/workflow-js/blob/main/examples/agents/app/email-analyzer/route.ts"
  >
    Intelligent email processing system for content analysis, classification,
    and automated responses.
  </Card>
  <Card
    title="Social Media Manager"
    icon="share-nodes"
    href="https://github.com/upstash/workflow-js/blob/main/examples/agents-instagram-post-generator"
  >
    Multi-platform social media management system for content moderation and
    engagement automation.
  </Card>
</CardGroup>

# Evaluator-Optimizer
Source: https://upstash.com/docs/workflow/agents/patterns/evaluator-optimizer

<img />

In this example, the generator creates output and passes it to the evaluator, which evaluates the response. If the evaluation fails, the evaluator returns corrections, and the generator is called again using the corrected output.

```ts
import { serve } from "@upstash/workflow/nextjs";
import { agentWorkflow } from "@upstash/workflow-agents";

export const { POST } = serve(async (context) => {
  const agents = agentWorkflow(context);
  const model = agents.openai('gpt-3.5-turbo');

  // Generator agent that generates content
  const generator = agents.agent({
    model,
    name: 'generator',
    maxSteps: 1,
    background: 'You are an agent that generates text based on a prompt.',
    tools: {}
  });

  // Evaluator agent that evaluates the text and gives corrections
  const evaluator = agents.agent({
    model,
    name: 'evaluator',
    maxSteps: 1,
    background: 'You are an agent that evaluates the generated text and provides corrections if needed.',
    tools: {}
  });

  let generatedText = '';
  let evaluationResult = '';

  const prompt = "Generate a short explanation of quantum mechanics.";
  let nextPrompt = prompt;
  for (let i = 0; i < 3; i++) {
    // Construct prompt for generator:
    // - If there's no evaluation, use the original prompt
    // - If there's an evaluation, provide the prompt, the last generated text, and the evaluator's feedback
    if (evaluationResult && evaluationResult !== "PASS") {
      nextPrompt = `Please revise the answer to the question "${prompt}". Previous answer was: "${generatedText}", which received this feedback: "${evaluationResult}".`;
    }

    // Generate content
    const generatedResponse = await agents.task({ agent: generator, prompt: nextPrompt }).run();
    generatedText = generatedResponse.text

    // Evaluate the generated content
    const evaluationResponse = await agents.task({ agent: evaluator, prompt: `Evaluate and provide feedback for the following text: ${generatedText}` }).run();
    evaluationResult = evaluationResponse.text

    // If the evaluator accepts the content (i.e., "PASS"), stop
    if (evaluationResult.includes("PASS")) {
      break;
    }
  }

  console.log(generatedText);
});
```

<img />

In response to the prompt, our agents generate this response:

```
Quantum mechanics is a branch of physics that describes the behavior of particles at the smallest scales, such as atoms and subatomic particles. It introduces the concept of quantized energy levels, wave-particle duality, and probabilistic nature of particles. In quantum mechanics, particles can exist in multiple states simultaneously until measured, and their behavior is governed by mathematical equations known as wave functions. This theory has revolutionized our understanding of the fundamental building blocks of the universe and has led to the development of technologies like quantum computing and quantum cryptography.
```

# Orchestrator-Workers
Source: https://upstash.com/docs/workflow/agents/patterns/orchestrator-workers

<img />

This workflow uses an orchestrator to direct multiple worker agents to handle different subtasks, then synthesizes their outputs.

```ts
import { serve } from "@upstash/workflow/nextjs";
import { agentWorkflow } from "@upstash/workflow-agents";
import { WikipediaQueryRun } from "@langchain/community/tools/wikipedia_query_run";

const wikiTool = new WikipediaQueryRun({
  topKResults: 1,
  maxDocContentLength: 500,
})

export const { POST } = serve(async (context) => {
  const agents = agentWorkflow(context);
  const model = agents.openai('gpt-4o');

  // Worker agents
  const worker1 = agents.agent({
    model,
    name: 'worker1',
    tools: { wikiTool },
    maxSteps: 3,
    background: 'You are a worker agent that answers general questions about advanced physics.'
  });

  const worker2 = agents.agent({
    model,
    name: 'worker2',
    tools: { wikiTool },
    maxSteps: 3,
    background: 'You are a worker agent that answers questions about quantum mechanics.'
  });

  const worker3 = agents.agent({
    model,
    name: 'worker3',
    tools: { wikiTool },
    maxSteps: 3,
    background: 'You are a worker agent that answers questions about relativity.'
  });

  // Synthesizing results
  const task = agents.task({
    model,
    prompt: `Create a Q&A for advanced topics in physics`,
    agents: [worker1, worker2, worker3],
    maxSteps: 3,
  })
  const { text } = await task.run();

  console.log(text);
});
```

If you wish to manually use the model results instead of utilizing a [manager LLM](/docs/workflow/agents/overview#multi-agents) as we did in this example, see the [parallelization example](/docs/workflow/agents/patterns/parallelization)

<img />

In response to the prompt, the agents create the following response:

```
### Quantum Mechanics

**Q: What is quantum mechanics?**
A: Quantum mechanics is a fundamental theory in physics that describes the behavior of nature at the atomic and subatomic levels. It serves as the foundation for all quantum physics, including quantum chemistry, quantum field theory, quantum technology, and quantum information science.

**Q: What are some key principles of quantum mechanics?**
A:
1. **Wave-Particle Duality**: Particles exhibit both wave-like and particle-like properties.
2. **Uncertainty Principle**: Certain pairs of physical properties, like position and momentum, cannot be simultaneously measured with arbitrary precision.
3. **Quantum Superposition**: A quantum system can exist in multiple states at once until it is measured.
4. **Quantum Entanglement**: Particles become interconnected such that the state of one influences the state of another, regardless of distance.
5. **Quantization**: Energy levels in quantum systems are discrete.
6. **Probability and Wave Functions**: Quantum systems are described by wave functions, which provide probabilities of finding a system in a particular state.
7. **Observer Effect**: Measurement affects the system being observed.

### Relativity

**Q: What is the theory of relativity?**
A: Developed by Albert Einstein, the theory of relativity encompasses two interrelated theories: special relativity and general relativity.

**Q: What is special relativity?**
A: Proposed by Einstein in 1905, special relativity addresses the relationship between space and time in the absence of gravity. It is based on two key postulates: the invariance of physical laws in all inertial frames and the constancy of the speed of light in a vacuum.

**Q: What is general relativity?**
A: Published by Einstein in 1915, general relativity is a geometric theory of gravitation. It describes gravity as a geometric property of space and time, or four-dimensional spacetime, and explains how massive objects cause a distortion in spacetime.

These topics challenge classical intuitions and have led to significant advancements in our understanding of the universe and the development of new technologies.
```

# Parallelization
Source: https://upstash.com/docs/workflow/agents/patterns/parallelization

<img />

This workflow calls multiple agents simultaneously to handle tasks, and then aggregates their results.

```ts
import { serve } from "@upstash/workflow/nextjs";
import { agentWorkflow } from "@upstash/workflow-agents";

export const { POST } = serve(async (context) => {
  const agents = agentWorkflow(context);
  const model = agents.openai('gpt-3.5-turbo');

  // Define worker agents
  const worker1 = agents.agent({
    model,
    name: 'worker1',
    maxSteps: 1,
    background: 'You are an agent that explains quantum physics.',
    tools: {}
  });

  const worker2 = agents.agent({
    model,
    name: 'worker2',
    maxSteps: 1,
    background: 'You are an agent that explains relativity.',
    tools: {}
  });

  const worker3 = agents.agent({
    model,
    name: 'worker3',
    maxSteps: 1,
    background: 'You are an agent that explains string theory.',
    tools: {}
  });

  // Await results
  const [result1, result2, result3] = await Promise.all([
    agents.task({ agent: worker1, prompt: "Explain quantum physics." }).run(),
    agents.task({ agent: worker2, prompt: "Explain relativity." }).run(),
    agents.task({ agent: worker3, prompt: "Explain string theory." }).run(),
  ]);

  // Aggregating results
  const aggregator = agents.agent({
    model,
    name: 'aggregator',
    maxSteps: 1,
    background: 'You are an agent that summarizes multiple answers.',
    tools: {}
  });

  const task = await agents.task({
    agent: aggregator,
    prompt: `Summarize these three explanations: ${result1.text}, ${result2.text}, ${result3.text}`
  })
  const finalSummary = await task.run();

  console.log(finalSummary.text);
});

```

You can also see how the same thing can be achieved using an manager agent in [orchestrator-workers example](/docs/workflow/agents/patterns/orchestrator-workers).

<img />

In response to the prompt, our agents generate this response:

```
Quantum physics explores the behavior of very small particles, such as atoms and subatomic particles, in the strange world of quantum mechanics, where particles can exist in multiple states simultaneously. Key principles include superposition and entanglement, leading to technological advancements like quantum computers. Relativity, developed by Albert Einstein, consists of special relativity and general relativity, explaining the behavior of objects moving at high speeds and the warping of spacetime by massive objects. String theory proposes that the universe's fundamental building blocks are tiny vibrating strings, aiming to unify the four fundamental forces of nature and suggest extra dimensions beyond the familiar ones.
```

# Prompt Chaining
Source: https://upstash.com/docs/workflow/agents/patterns/prompt-chaining

<img />

This workflow involves chaining multiple LLM calls, where the output of one agent becomes the input for the next agent.

```ts
import { serve } from "@upstash/workflow/nextjs";
import { agentWorkflow } from "@upstash/workflow-agents";
import { WikipediaQueryRun } from "@langchain/community/tools/wikipedia_query_run";

export const { POST } = serve(async (context) => {
  const agents = agentWorkflow(context);
  const model = agents.openai('gpt-3.5-turbo');

  const agent1 = agents.agent({
    model,
    name: 'firstAgent',
    maxSteps: 1,
    background: 'You are an agent that lists famous physicists.',
    tools: {}
  });

  const agent2 = agents.agent({
    model,
    name: 'secondAgent',
    // set to 2 as this agent will first request tools
    // and then summarize them:
    maxSteps: 2,
    background:
      'You are an agent that describes the work of' +
      ' the physicists listed in the previous prompt.',
    tools: {
      wikiTool: new WikipediaQueryRun({
        topKResults: 1,
        maxDocContentLength: 500,
      })
    }
  });

  const agent3 = agents.agent({
    model,
    name: 'thirdAgent',
    maxSteps: 1,
    background:
      'You are an agent that summarizes the ' +
      'works of the physicists mentioned previously.',
    tools: {}
  });

  // Chaining agents
  const firstOutput = await agents.task({
    agent: agent1,
    prompt: "List 3 famous physicists."
  }).run();

  const secondOutput = await agents.task({
    agent: agent2,
    prompt: `Describe the work of: ${firstOutput.text}`
  }).run();

  const { text } = await agents.task({
    agent: agent3,
    prompt: `Summarize: ${secondOutput.text}`
  }).run();

  console.log(text);
});
```

<img />

In response to the prompt, our agents generate this response:

```
Albert Einstein was a German physicist known for his theory of relativity and the famous equation E=mc^2. He made significant contributions to quantum mechanics and was awarded the Nobel Prize in Physics in 1921.

Isaac Newton, an English polymath, was a key figure in the Scientific Revolution and the Enlightenment. He is famous for his laws of motion and universal gravitation, as outlined in his book "PhilosophiÃ¦ Naturalis Principia Mathematica."

Marie Curie, a Polish-French physicist and chemist, conducted pioneering research on radioactivity and was the first woman to win a Nobel Prize. She is the only person to win Nobel Prizes in two scientific fields and her work has had a lasting impact on physics and chemistry.
```

# Bulk Delete Failed Workflow Runs
Source: https://upstash.com/docs/workflow/api-reference/dlq/bulk-delete-failed-workflow-runs

/workflow/openapi.yaml delete /v2/workflows/dlq
Delete multiple failed workflow runs from the DLQ.

When a workflow run fails, it is moved to the DLQ. You can manually remove a failed workflow run from the DLQ using this endpoint. This is useful for cleaning up failed runs that you no longer wish to retry or analyze.

You can either specify specific DLQ IDs to delete, or use filters to delete matching workflows.
When using filters without DLQ IDs, the operation supports pagination via cursor.

# Bulk Restart Workflows from DLQ
Source: https://upstash.com/docs/workflow/api-reference/dlq/bulk-restart-workflows-from-dlq

/workflow/openapi.yaml post /v2/workflows/dlq/restart
Restart multiple failed workflow runs from the DLQ. Each workflow will start from the beginning.

Unlike resume, which continues from where workflows failed, restart executes the entire workflows
from the first step. New workflow run IDs are generated and all steps will be executed again.

A maximum of 50 workflow runs can be restarted per request. If more runs are available, a cursor is returned, which can be used in subsequent requests to continue the operation. When no cursor is returned, all entries have been processed.
Each restarted workflow run is assigned a new random Run ID.

# Bulk Resume Workflows from DLQ
Source: https://upstash.com/docs/workflow/api-reference/dlq/bulk-resume-workflows-from-dlq

/workflow/openapi.yaml post /v2/workflows/dlq/resume
When a workflow run fails, it's automatically moved to the DLQ (Dead Letter Queue) where it can be analyzed and resumed. 
  The resume feature allows you to continue a failed workflow run from exactly where it failed, without re-executing successfully completed steps.

  This is particularly useful for long-running workflows where you don't want to lose progress from successful steps when a single step fails.

  When a workflow is resumed, it continues execution from the last failed step. A new workflow run ID is generated,
  but the workflow maintains the state and results from previously completed steps.

  <Note>
    You can make changes to the workflow code as long as these changes come after the failed steps. 
    However, making changes before the failed step will break the code and is not allowed.

    For more details, check out [Handle workflow route code changes](/docs/workflow/howto/changes) page.
  </Note>

# Cancel In-Progress Failure Callback
Source: https://upstash.com/docs/workflow/api-reference/dlq/cancel-in-progress-failure-callback

/workflow/openapi.yaml delete /v2/workflows/dlq/callback/{dlqId}
Cancel an in-progress failure callback for a failed workflow.

When you retry a failed failure callback using the POST endpoint, it enters an in-progress state.
This endpoint allows you to cancel that in-progress callback before it completes.

The callback must be in an in-progress state. If there's no in-progress callback,
the request will fail with a 400 error.

# Delete Failed Workflow Run
Source: https://upstash.com/docs/workflow/api-reference/dlq/delete-failed-workflow-run

/workflow/openapi.yaml delete /v2/workflows/dlq/{dlqId}
Delete a specific failed workflow run from the DLQ.

Use this endpoint to remove a workflow from the DLQ after you have addressed the issue
or determined that the workflow should not be retried.

# Get Failed Workflow Run
Source: https://upstash.com/docs/workflow/api-reference/dlq/get-failed-workflow-run

/workflow/openapi.yaml get /v2/workflows/dlq/{dlqId}
Get details of a specific failed workflow run from the DLQ.

# List Failed Workflow Runs
Source: https://upstash.com/docs/workflow/api-reference/dlq/list-failed-workflow-runs

/workflow/openapi.yaml get /v2/workflows/dlq
List and paginate through all failed workflow runs currently in the DLQ.

Failed workflows end up in the DLQ after exhausting all retry attempts. You can filter,
paginate, and inspect these failures to understand what went wrong and decide whether to
resume, restart, or delete them.

# Restart Workflow from DLQ
Source: https://upstash.com/docs/workflow/api-reference/dlq/restart-workflow-from-dlq

/workflow/openapi.yaml post /v2/workflows/dlq/restart/{dlqId}
Restart a failed workflow run from the DLQ. The workflow will start from the beginning.

Unlike resume, which continues from where the workflow failed, restart executes the entire workflow
from the first step. A new workflow run ID is generated and all steps will be executed again.

# Resume Workflow from DLQ
Source: https://upstash.com/docs/workflow/api-reference/dlq/resume-workflow-from-dlq

/workflow/openapi.yaml post /v2/workflows/dlq/resume/{dlqId}

# Retry Failure Callback
Source: https://upstash.com/docs/workflow/api-reference/dlq/retry-failure-callback

/workflow/openapi.yaml post /v2/workflows/dlq/callback/{dlqId}
If the failure callback for a workflow run has failed, you can use this endpoint to manually trigger the failure callback again. 
This is useful for ensuring that your system is notified of workflow failures even if the original callback attempt did not succeed.

The state of the failure callback for each workflow run is included in the DLQ message response as failureCallbackInfo.state. 
You can filter for all workflow runs with a failed failure callback by using the failureCallbackState filter when listing workflow runs in the DLQ with the `/v2/workflows/dlq` endpoint.

# Get Flow Control Key
Source: https://upstash.com/docs/workflow/api-reference/flow-control/get-flow-control-key

/workflow/openapi.yaml get /v2/flowControl/{flowControlKey}
Get details of a specific Flow Control key

# Get Flow Control Key
Source: https://upstash.com/docs/workflow/api-reference/flow-control/get-flow-control-key-1

/workflow/openapi.yaml get /v2/keys/rotate
Get details of a specific Flow Control key.

Flow Control keys are used to manage concurrency and rate limiting for workflow steps.
This endpoint returns the current waitlist size for the specified flow control key,
which indicates how many workflow steps are currently waiting due to flow control constraints.

# Get Global Parallelism
Source: https://upstash.com/docs/workflow/api-reference/flow-control/get-global-parallelism

/workflow/openapi.yaml get /v2/globalParallelism
Returns the current global parallelism usage across all flow control keys

# List Flow Control Keys
Source: https://upstash.com/docs/workflow/api-reference/flow-control/list-flow-control-keys

/workflow/openapi.yaml get /v2/flowControl
List all Flow Control keys

# Pause Flow Control Key
Source: https://upstash.com/docs/workflow/api-reference/flow-control/pause-flow-control-key

/workflow/openapi.yaml post /v2/flowControl/{flowControlKey}/pause
Pauses the delivery of messages associated with a specific flow-control key.

# Pin Configuration for Flow Control Key
Source: https://upstash.com/docs/workflow/api-reference/flow-control/pin-configuration-for-flow-control-key

/workflow/openapi.yaml post /v2/flowControl/{flowControlKey}/pin
Pins a processing configuration for a specific flow-control key.

# Reset Rate for Flow Control Key
Source: https://upstash.com/docs/workflow/api-reference/flow-control/reset-rate-for-flow-control-key

/workflow/openapi.yaml post /v2/flowControl/{flowControlKey}/resetRate
Resets the rate configuration state for a specific flow-control key.

# Resume Flow Control Key
Source: https://upstash.com/docs/workflow/api-reference/flow-control/resume-flow-control-key

/workflow/openapi.yaml post /v2/flowControl/{flowControlKey}/resume
Resumes the delivery of messages associated with a specific flow-control key.

# Unpin Configuration for Flow Control Key
Source: https://upstash.com/docs/workflow/api-reference/flow-control/unpin-configuration-for-flow-control-key

/workflow/openapi.yaml post /v2/flowControl/{flowControlKey}/unpin
Removes the pinned configuration for a specific flow-control key.

# List Workflow Run Logs
Source: https://upstash.com/docs/workflow/api-reference/logs/list-workflow-run-logs

/workflow/openapi.yaml get /v2/workflows/logs

# List Waiters
Source: https://upstash.com/docs/workflow/api-reference/notify/list-waiters

/workflow/openapi.yaml get /v2/waiters/{eventId}
List all active waiters for a specific event ID.

Returns information about all workflow runs that are currently waiting for the specified event.

# Notify Event
Source: https://upstash.com/docs/workflow/api-reference/notify/notify-event

/workflow/openapi.yaml post /v2/notify/{eventId}
Notify an event to all waiters listening for that event ID.

This endpoint broadcasts an event to all active waiters that are waiting for the specified event.
Each waiting workflow step receives the event data and continues execution.

**Important:** This is an "unsafe notify" operation - if there are no active waiters when the
notification is sent, the event is lost and will not be delivered later. For guaranteed delivery
within a specific workflow run, use the `/v2/notify/{workflowRunId}/{eventId}` endpoint instead.

# Notify Workflow Run Event
Source: https://upstash.com/docs/workflow/api-reference/notify/notify-workflow-run-event

/workflow/openapi.yaml post /v2/notify/{workflowRunId}/{eventId}
Notify an event to a specific workflow run's waiters.

This endpoint sends an event notification to waiters within a specific workflow run.
Unlike the general notify endpoint, this uses a "safe notify" mechanism that guarantees
delivery even if no waiter is currently active.

**Safe Notify Behavior:**
- If a waiter is currently active, it receives the notification immediately
- If no waiter is active, the notification is saved in the database
- When a waiter becomes active later, it will consume the saved notification
- This ensures events are never lost due to timing issues

# Batch Trigger Workflow Runs
Source: https://upstash.com/docs/workflow/api-reference/runs/batch-trigger-workflow-runs

/workflow/openapi.yaml post /v2/batch/trigger
Start multiple workflow runs in a single request.

# Bulk Cancel Workflow Runs
Source: https://upstash.com/docs/workflow/api-reference/runs/bulk-cancel-workflow-runs

/workflow/openapi.yaml delete /v2/workflows/runs
Cancel all matching workflow runs.

# Cancel Workflow Run
Source: https://upstash.com/docs/workflow/api-reference/runs/cancel-workflow-run

/workflow/openapi.yaml delete /v2/workflows/runs/{workflowRunId}
Cancel an ongoing workflow run.

# Trigger Workflow Run
Source: https://upstash.com/docs/workflow/api-reference/runs/trigger-workflow-run

/workflow/openapi.yaml post /v2/trigger/{workflowUrl}
Start a new workflow run.

# Get Signing Keys
Source: https://upstash.com/docs/workflow/api-reference/signing-keys/get-signing-keys

/workflow/openapi.yaml get /v2/keys
Retrieve your current and next signing keys

# Rotate Signing Keys
Source: https://upstash.com/docs/workflow/api-reference/signing-keys/rotate-signing-keys

/workflow/openapi.yaml post /v2/keys/rotate
Rotate your signing keys

# Caveats
Source: https://upstash.com/docs/workflow/basics/caveats

## Introduction

In this guide, we'll look at best practices and caveats for using Upstash Workflow.

## Core Principles

### Execute business logic in `context.run`

Your workflow endpoint will be called multiple times during a workflow run. Therefore:

* Place your business logic code inside the `context.run` function for each step
* Code outside `context.run` only serves to connect steps

Example:

<CodeGroup>

```typescript api/workflow/route.ts
export const { POST } = serve<string>(async (context) => {
  const input = context.requestPayload

  const result = await context.run("step-1", () => {
    return { success: true }
  })

  console.log("This log will appear multiple times")

  await context.run("step-2", () => {
    console.log("This log will appear just once")
    console.log("Step 1 status is:", result.success)
  })
})
```

```python main.py
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:
    input = context.request_payload

    async def _step_1() -> Dict:
        return {"success": True}

    result = await context.run("step-1", _step_1)

    print("This log will appear multiple times")

    async def _step_2() -> None:
        print("This log will appear just once")
        print("Step 1 status is:", result["success"])

    await context.run("step-2", _step_2)

```

</CodeGroup>

### Return Results from context.run for Later Use

Always return step results if needed in subsequent steps.

<CodeGroup>

```typescript ❌ Incorrect - TypeScript
export const { POST } = serve<string>(async (context) => {
  const input = context.requestPayload

  let result

  await context.run("step-1", async () => {
    result = await someWork(input)
  })
  await context.run("step-2", async () => {
    await someOtherWork(result)
  })
})
```

```typescript ✅ Correct - TypeScript
export const { POST } = serve<string>(async (context) => {
  const input = context.requestPayload

  const result = await context.run("step-1", async () => {
    return await someWork(input)
  })

  await context.run("step-2", async () => {
    someOtherWork(result)
  })
})
```

```python ❌ Incorrect - Python
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:
    input = context.request_payload

    result = None

    async def _step_1() -> Dict:
        nonlocal result
        result = await some_work(input)

    await context.run("step-1", _step_1)

    async def _step_2() -> None:
        await some_other_work(result)

    await context.run("step-2", _step_2)

```

```python ✅ Correct - Python
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:
    input = context.request_payload

    async def _step_1() -> Dict:
        return await some_work(input)

    result = await context.run("step-1", _step_1)

    async def _step_2() -> None:
        await some_other_work(result)

    await context.run("step-2", _step_2)

```

</CodeGroup>

Because your workflow endpoint is called multiple times, `result` will be unitialized when the endpoint is called again to run `step-2`.

If you are curious about why an endpoint is called multiple times, see [how Workflow works](/docs/workflow/basics/how).

## Avoiding Common Pitfalls

### Avoid Non-deterministic Code Outside `context.run`

A workflow endpoint should always produce the same results, even if it's called multiple times. Avoid:

* Non-idempotent functions
* Time-dependent code
* Randomness

Example of what to avoid:

<CodeGroup>

```typescript ❌ Non-idempotent functions - TypeScript
export const { POST } = serve<{ entryId: string }>(async (context) => {
  const { entryId } = context.requestPayload;

  // 👇 Problem: Non-idempotent function outside context.run:
  const result = await getResultFromDb(entryId);
  if (result.return) {
    return;
  }

  // ...
})
```

```typescript ❌ Time-dependent code - TypeScript
export const { POST } = serve<string>(async (context) => {
  const input = context.requestPayload

  // 👇 Problem: time-dependent code
  if (Date.now() % 5 == 2) {
    await context.run("step-1", () => {
      // ...
    })
  } else {
    await context.run("step-2", () => {
      // ...
    })
  }
})
```

```typescript ❌ Random code - TypeScript
export const { POST } = serve<string>(async (context) => {
  const input = context.requestPayload

  // 👇 Problem: random code
  if (Math.floor(Math.random() * 10) % 5 == 2) {
    await context.run("step-1", () => {
      // ...
    })
  } else {
    await context.run("step-2", () => {
      // ...
    })
  }
})
```

```python ❌ Non-idempotent functions - Python
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:
    entry_id = context.request_payload["entry_id"]

    # 👇 Problem: Non-idempotent function outside context.run:
    result = await get_result_from_db(entry_id)
    if result.should_return:
        return

    # ...

```

```python ❌ Time-dependent code - Python
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:
    input = context.request_payload

    # 👇 Problem: time-dependent code
    if time.time() % 5 == 2:
        await context.run("step-1", lambda: ...)
    else:
        await context.run("step-2", lambda: ...)

```

```python ❌ Random code - Python
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:
    input = context.request_payload

    # 👇 Problem: random code
    if random.randint(0, 9) % 5 == 2:
        await context.run("step-1", lambda: ...)
    else:
        await context.run("step-2", lambda: ...)

```

</CodeGroup>

If you implement a non-idempotent code like the one shown above, you might encounter `Failed to authenticate Workflow request.` errors. This can happen if you `return` based on the result of the non-idempotent code before any workflow step.

To prevent this, ensure that the non-idempotent code (such as `getResultFromDb` in the example) runs within `context.run`.

<CodeGroup>

```typescript TypeScript
const result = await context.run(async () => {
  await getResultFromDb(entryId)
});
if (result.return) {
  return;
}
```

```python Python
async def _get_result_from_db():
    return await get_result_from_db(entry_id)

result = await context.run("get-result-from-db", _get_result_from_db)

if result.should_return:
    return

```

</CodeGroup>

### Ensure Idempotency in `context.run`

Business logic should be idempotent due to potential retries in distributed systems. In other words, **when a workflow runs twice with the same input, the end result should be the same as if the workflow only ran once**.

In the example below, the `someWork` function must be idempotent:

<CodeGroup>

```typescript api/workflow/route.ts
export const { POST } = serve<string>(async (context) => {
  const input = context.requestPayload

  await context.run("step-1", async () => {
    return someWork(input)
  })
})
```

```python main.py
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:
    input = context.request_payload

    async def _step_1() -> None:
        return await some_work(input)

    await context.run("step-1", _step_1)

```

</CodeGroup>

Imagine that `someWork` executes once and makes a change to a database. However, before the database had a chance to respond with the successful change, the connection is lost. Your Workflow cannot know if the database change was successful or not. The caller has no choice but to retry, which will cause `someWork` to run twice.

If `someWork` is not idempotent, this could lead to unintended consequences. For example duplicated records or corrupted data. Idempotency is crucial to maintaining the integrity and reliability of your workflow.

### Don't Nest Context Methods

Avoid calling `context.call`, `context.sleep`, `context.sleepFor`, or `context.run` within another `context.run`.

<CodeGroup>

```typescript api/workflow/route.ts
import { serve } from "@upstash/workflow/nextjs"

export const { POST } = serve<string>(async (context) => {
  const input = context.requestPayload

  await context.run("step-1", async () => {
    await context.sleep(...) // ❌ INCORRECT
    await context.run(...) // ❌ INCORRECT
    await context.call(...) // ❌ INCORRECT
  })
})
```

```python main.py
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:
    input = context.request_payload

    async def _step_1() -> None:
        await context.sleep(...) #  ❌ INCORRECT
        await context.run(...) #  ❌ INCORRECT
        await context.call(...) #  ❌ INCORRECT

    await context.run("step-1", _step_1)

```

</CodeGroup>

### Include At Least One Step in Workflow

Every workflow must include at least one step execution with `context.run`. If no steps are defined, the workflow will throw a `Failed to authenticate Workflow request.` error.

<CodeGroup>

```typescript ❌ Missing steps - TypeScript
export const { POST } = serve<string>(async (context) => {
  const input = context.requestPayload

  // 👇 Problem: No context.run call
  console.log("Processing input:", input)

  // This workflow will fail with "Failed to authenticate Workflow request."
})
```

```typescript ✅ Correct - TypeScript
export const { POST } = serve<string>(async (context) => {
  const input = context.requestPayload

  // 👇 At least one step is required
  await context.run("dummy-step", async () => {
    return
  })
})
```

```python ❌ Missing steps - Python
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:
    input = context.request_payload

    # 👇 Problem: No context.run call
    print("Processing input:", input)

    # This workflow will fail with "Failed to authenticate Workflow request."
```

```python ✅ Correct - Python
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:
    input = context.request_payload

    # 👇 At least one step is required
    async def _dummy_step():
        return

    await context.run("dummy-step", _dummy_step)
```

</CodeGroup>

Even for the placeholder implementations, you must include one dummy step for the Workflow authentication mechanism to function properly.

### Avoid Promise.any

In workflow-js, you can use [`Promise.all` to run steps in parallel](/docs/workflow/howto/parallel-runs). However, a similar method, [`Promise.any`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any), is not supported for workflow steps.

While `Promise.all` works seamlessly, `Promise.any` does not currently function with workflow steps. We are exploring the possibility of adding support for `Promise.any` in the future.

If you have a specific use case that requires `Promise.any`, don't hesitate to reach out to Upstash support.

# Overview
Source: https://upstash.com/docs/workflow/basics/client

The Workflow Client lets you programmatically interact with your workflow runs.
You can use it from the same application that hosts your workflows, or from any external service.

## Initialization

Initialize a new client with your credentials:

```javascript
import { Client } from "@upstash/workflow"

const client = new Client({
  baseUrl: process.env.QSTASH_URL!,
  token: process.env.QSTASH_TOKEN!
})
```

The client is lightweight and stateless. You can safely reuse a single instance across your application.

## Functionality

The client exposes a set of functions to manage workflow runs and inspect their state:

* [client.trigger](/docs/workflow/basics/client/trigger)
* [client.cancel](/docs/workflow/basics/client/cancel)
* [client.notify](/docs/workflow/basics/client/notify)
* [client.logs](/docs/workflow/basics/client/logs)
* [client.getWaiters](/docs/workflow/basics/client/waiters)
* client.dlq
    * [client.dlq.list](/docs/workflow/basics/client/dlq/list)
    * [client.dlq.restart](/docs/workflow/basics/client/dlq/restart)
    * [client.dlq.resume](/docs/workflow/basics/client/dlq/resume)
    * [client.dlq.delete](/docs/workflow/basics/client/dlq/delete)
    * [client.dlq.retryFailureFunction](/docs/workflow/basics/client/dlq/callback)

# client.cancel
Source: https://upstash.com/docs/workflow/basics/client/cancel

Cancel one or more workflow runs. You can pass IDs directly, use filters, or cancel all at once.

## Arguments

The `cancel` method accepts one of the following:

### By ID

Pass a single workflow run ID or an array of IDs directly:

```ts
await client.cancel("<WORKFLOW_RUN_ID>");
await client.cancel(["<ID_1>", "<ID_2>"]);
```

### By filters

Pass an object with a `filter` field containing one or more filter criteria:

<ParamField body="filter" type="object">
    <Expandable defaultOpen>
        <ParamField body="workflowUrl" type="string" optional>
            Cancel workflows matching this exact URL. Cannot be combined with `workflowUrlStartingWith`.
        </ParamField>

        <ParamField body="workflowUrlStartingWith" type="string" optional>
            Cancel workflows whose URL starts with this prefix. Cannot be combined with `workflowUrl`.
        </ParamField>

        <ParamField body="label" type="string | string[]" optional>
            Cancel workflows with this label. Pass an array to match runs that
            have any of the given labels (OR semantics).
        </ParamField>

        <ParamField body="fromDate" type="Date | number" optional>
            Cancel workflows created after this date. Accepts `Date` objects or Unix timestamps (ms).
        </ParamField>

        <ParamField body="toDate" type="Date | number" optional>
            Cancel workflows created before this date. Accepts `Date` objects or Unix timestamps (ms).
        </ParamField>

        <ParamField body="callerIp" type="string" optional>
            Cancel workflows triggered by this IP address.
        </ParamField>

        <ParamField body="flowControlKey" type="string" optional>
            Cancel workflows with this flow control key.
        </ParamField>
    </Expandable>
</ParamField>

<ParamField body="count" type="number" optional>
    Maximum number of workflow runs to cancel per call. Defaults to `100`.
</ParamField>

### Cancel all

<ParamField body="all" type="boolean">
    Set to `true` to cancel all pending and active workflow runs.
</ParamField>

## Response

<ResponseField name="cancelled" type="number">
    The number of workflow runs that were cancelled.
</ResponseField>

## Usage

### Cancel by ID

```ts
// cancel a single workflow
await client.cancel("<WORKFLOW_RUN_ID>");

// cancel a set of workflow runs
await client.cancel(["<WORKFLOW_RUN_ID_1>", "<WORKFLOW_RUN_ID_2>"]);
```

### Cancel with URL filter

Cancel all workflow runs on a specific endpoint using a URL prefix:

```ts
await client.cancel({
  filter: { workflowUrlStartingWith: "https://your-endpoint.com" }
});
```

For an exact URL match:

```ts
await client.cancel({
  filter: { workflowUrl: "https://your-endpoint.com/api/workflow" }
});
```

### Cancel with filters

You can combine multiple filter parameters:

```ts
// cancel by label
await client.cancel({ filter: { label: "my-label" } });

// cancel by label and date range
await client.cancel({
  filter: {
    label: "my-label",
    fromDate: 1640995200000,
  }
});

// cancel by URL and date range
await client.cancel({
  filter: {
    workflowUrl: "https://your-endpoint.com/api/workflow",
    fromDate: new Date("2024-01-01"),
    toDate: new Date("2024-06-01"),
  }
});
```

### Cancel _all_ workflows

```ts
let cancelled: number;
do {
  const result = await client.cancel({ all: true });
  cancelled = result.cancelled;
} while (cancelled > 0);
```

# client.dlq.retryFailureFunction
Source: https://upstash.com/docs/workflow/basics/client/dlq/callback

If a workflow's `failureFunction` or `failureUrl` request has failed, you can retry it using the `retryFailureFunction` method:

## Arguments

## Response

## Usage
```ts
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" });

// Retry the failure callback for a specific DLQ message
const response = await client.dlq.retryFailureFunction({
  dlqId: "dlq-12345" // The ID of the DLQ message to retry
});
```

# client.dlq.delete
Source: https://upstash.com/docs/workflow/basics/client/dlq/delete

The `dlq.delete` method deletes one or more workflow runs from the **Dead Letter Queue (DLQ)**.

## Arguments

You can delete DLQ entries by ID, by an array of IDs, or by filters.

### By DLQ ID

Pass a single DLQ ID or an array of IDs directly:

```ts
await client.dlq.delete("dlq-123");
await client.dlq.delete(["dlq-123", "dlq-456"]);
```

Or as an object with `dlqIds`:

```ts
await client.dlq.delete({ dlqIds: ["dlq-123", "dlq-456"] });
```

### By filters

<ParamField body="filter" type="object">
    <Expandable defaultOpen>
        <ParamField body="workflowUrl" type="string" optional>
            Filter by exact workflow URL.
        </ParamField>

        <ParamField body="workflowRunId" type="string" optional>
            Filter by workflow run ID.
        </ParamField>

        <ParamField body="label" type="string | string[]" optional>
            Delete workflows with this label. Pass an array to match runs that
            have any of the given labels (OR semantics).
        </ParamField>

        <ParamField body="fromDate" type="Date | number" optional>
            Delete workflows created after this date. Accepts `Date` objects or Unix timestamps (ms).
        </ParamField>

        <ParamField body="toDate" type="Date | number" optional>
            Delete workflows created before this date. Accepts `Date` objects or Unix timestamps (ms).
        </ParamField>

        <ParamField body="callerIp" type="string" optional>
            Filter by the IP address that triggered the workflow.
        </ParamField>

        <ParamField body="flowControlKey" type="string" optional>
            Filter by flow control key.
        </ParamField>

        <ParamField body="workflowCreatedAt" type="number" optional>
            Filter by workflow creation time (Unix timestamp in ms).
        </ParamField>

        <ParamField body="failureFunctionState" type="string" optional>
            Filter by failure callback state.
        </ParamField>
    </Expandable>
</ParamField>

<ParamField body="count" type="number" optional>
    Maximum number of messages to process per call. Defaults to `100`.
</ParamField>

<ParamField body="cursor" type="string" optional>
    A pagination cursor from a previous request.
</ParamField>

### Delete all

<ParamField body="all" type="boolean">
    Set to `true` to delete all DLQ entries.
</ParamField>

## Response

<ResponseField name="deleted" type="number">
    The number of DLQ entries that were deleted.
</ResponseField>

<ResponseField name="cursor" type="string">
    A pagination cursor. If not returned, all matching entries have been processed.
</ResponseField>

## Usage

<CodeGroup>
```ts Single
await client.dlq.delete("dlq-123");
```
```ts Multiple
await client.dlq.delete(["dlq-123", "dlq-456"]);
```
```ts By filters
// delete by label
let cursor: string | undefined;
do {
  const result = await client.dlq.delete({
    filter: { label: "my-label" },
    cursor,
  });
  cursor = result.cursor;
} while (cursor);
```
```ts All
let cursor: string | undefined;
do {
  const result = await client.dlq.delete({ all: true, cursor });
  cursor = result.cursor;
} while (cursor);
```
</CodeGroup>

# client.dlq.list
Source: https://upstash.com/docs/workflow/basics/client/dlq/list

The `dlq.list` method retrieves messages that were sent to the **Dead Letter Queue (DLQ)**.

DLQ messages represent failed workflow or QStash deliveries that could not be retried successfully.

## Arguments

<ParamField body="cursor" type="string" optional>
    A pagination cursor from a previous request.
    Use this to fetch the next batch of results.
</ParamField>

<ParamField body="count" type="number" optional>
    Maximum number of DLQ messages to return.
    Defaults to a system-defined limit if not provided.
</ParamField>

<ParamField body="filter" type="object" optional>
    Filter options for narrowing down DLQ messages.

    <Expandable defaultOpen>
        <ParamField body="workflowUrl" type="string" optional>
            Filter by exact workflow URL.
        </ParamField>

        <ParamField body="workflowRunId" type="string" optional>
            Filter by workflow run ID.
        </ParamField>

        <ParamField body="label" type="string | string[]" optional>
            Filter by workflow label. Pass an array to match runs that have any
            of the given labels (OR semantics).
        </ParamField>

        <ParamField body="fromDate" type="Date | number" optional>
            Earliest date to include. Accepts `Date` objects or Unix timestamps (ms).
        </ParamField>

        <ParamField body="toDate" type="Date | number" optional>
            Latest date to include. Accepts `Date` objects or Unix timestamps (ms).
        </ParamField>

        <ParamField body="callerIp" type="string" optional>
            Filter by the IP address that triggered the workflow.
        </ParamField>

        <ParamField body="flowControlKey" type="string" optional>
            Filter by flow control key.
        </ParamField>

        <ParamField body="workflowCreatedAt" type="number" optional>
            Filter by workflow creation time (Unix timestamp in ms).
        </ParamField>

        <ParamField body="failureFunctionState" type="string" optional>
            Filter by failure callback state.
        </ParamField>
    </Expandable>
</ParamField>

## Response

<ResponseField name="messages" type="DLQMessage[]">
    An array of DLQ messages that match the provided filters.
</ResponseField>

<ResponseField name="cursor" type="string">
    A cursor to paginate through additional results.
    If not returned, you have reached the end of the DLQ.
</ResponseField>

## Usage

```ts
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" });

// List all DLQ messages
const { messages, cursor } = await client.dlq.list();

// List with pagination and filtering
const result = await client.dlq.list({
  cursor,
  count: 10,
  filter: {
    fromDate: Date.now() - 86400000, // last 24 hours
    toDate: Date.now(),
    workflowUrl: "https://your-endpoint.com",
    label: "my-workflow",
  }
});
```

# client.dlq.restart
Source: https://upstash.com/docs/workflow/basics/client/dlq/restart

The `dlq.restart` method restarts one or more workflow runs from **Dead Letter Queue (DLQ)**.
This allows you to reprocess workflow runs that previously failed after exhausting retries.

## Arguments

The first argument specifies which DLQ entries to restart. The optional second argument provides flow control and retry settings.

### By DLQ ID

Pass a single DLQ ID or an array of IDs directly:

```ts
await client.dlq.restart("dlq-12345");
await client.dlq.restart(["dlq-12345", "dlq-67890"]);
```

### By filters

Pass an object with a `filter` field:

<ParamField body="filter" type="object">
    <Expandable defaultOpen>
        <ParamField body="workflowUrl" type="string" optional>
            Filter by exact workflow URL.
        </ParamField>

        <ParamField body="workflowRunId" type="string" optional>
            Filter by workflow run ID.
        </ParamField>

        <ParamField body="label" type="string | string[]" optional>
            Restart workflows with this label. Pass an array to match runs that
            have any of the given labels (OR semantics).
        </ParamField>

        <ParamField body="fromDate" type="Date | number" optional>
            Restart workflows created after this date.
        </ParamField>

        <ParamField body="toDate" type="Date | number" optional>
            Restart workflows created before this date.
        </ParamField>

        <ParamField body="callerIp" type="string" optional>
            Filter by the IP address that triggered the workflow.
        </ParamField>

        <ParamField body="flowControlKey" type="string" optional>
            Filter by flow control key.
        </ParamField>

        <ParamField body="workflowCreatedAt" type="number" optional>
            Filter by workflow creation time (Unix timestamp in ms).
        </ParamField>

        <ParamField body="failureFunctionState" type="string" optional>
            Filter by failure callback state.
        </ParamField>
    </Expandable>
</ParamField>

<ParamField body="count" type="number" optional>
    Maximum number of messages to process per call. Defaults to `100`.
</ParamField>

<ParamField body="cursor" type="string" optional>
    A pagination cursor from a previous request.
</ParamField>

### Restart all

<ParamField body="all" type="boolean">
    Set to `true` to restart all DLQ entries.
</ParamField>

### Options (second argument)

<ParamField body="flowControl" type="object" optional>
    An optional flow control configuration to limit concurrency and execution rate of restarted workflow runs.

    See [Flow Control](/docs/workflow/features/flow-control) for details.

    <Expandable title="properties">
        <ParamField body="key" type="string">
          A logical grouping key that identifies which requests share the same flow control limits.
        </ParamField>

        <ParamField body="rate" type="number">
            The maximum number of allowed requests per second.
        </ParamField>

        <ParamField body="parallelism" type="number">
            The maximum number of concurrent requests allowed.
        </ParamField>

        <ParamField body="period" type="string|number">
            The time window used to enforce the defined rate limit. Default is `1s`.
        </ParamField>
    </Expandable>

</ParamField>

<ParamField body="retries" type="number" optional>
    Number of retry attempts to apply to the restarted workflow invocation.
    Defaults to `3` if not provided.
</ParamField>

## Response

<ResponseField name="cursor" type="string">
    A pagination cursor. If not returned, all matching entries have been processed.
</ResponseField>

<ResponseField name="workflowRuns" type="object[]">
    <Expandable defaultOpen>
        <ResponseField name="workflowRunId" type="string">
            The ID of the new workflow run created from the restarted DLQ message.
        </ResponseField>

        <ResponseField name="workflowCreatedAt" type="number">
            The Unix timestamp (in milliseconds) when the new workflow run was created.
        </ResponseField>
    </Expandable>
</ResponseField>

## Usage

<CodeGroup>
```ts Single
const { messages } = await client.dlq.list();

const response = await client.dlq.restart(messages[0].dlqId, {
  flowControl: {
    key: "my-flow-control-key",
    parallelism: 10,
  },
  retries: 3,
});
```
```ts Multiple
const response = await client.dlq.restart(["dlq-12345", "dlq-67890"]);
```
```ts By filters
let cursor: string | undefined;
do {
  const result = await client.dlq.restart({
    filter: { label: "my-label" },
    cursor,
  });
  cursor = result.cursor;
} while (cursor);
```
```ts All
let cursor: string | undefined;
do {
  const result = await client.dlq.restart({ all: true, cursor });
  cursor = result.cursor;
} while (cursor);
```
</CodeGroup>

# client.dlq.resume
Source: https://upstash.com/docs/workflow/basics/client/dlq/resume

The `dlq.resume` method resumes one or more workflow runs from the **Dead Letter Queue (DLQ)** at the point where they previously failed.
This allows you to continue execution from the failed step instead of restarting the workflow from the beginning.

## Arguments

The first argument specifies which DLQ entries to resume. The optional second argument provides flow control and retry settings.

### By DLQ ID

Pass a single DLQ ID or an array of IDs directly:

```ts
await client.dlq.resume("dlq-12345");
await client.dlq.resume(["dlq-12345", "dlq-67890"]);
```

### By filters

Pass an object with a `filter` field:

<ParamField body="filter" type="object">
    <Expandable defaultOpen>
        <ParamField body="workflowUrl" type="string" optional>
            Filter by exact workflow URL.
        </ParamField>

        <ParamField body="workflowRunId" type="string" optional>
            Filter by workflow run ID.
        </ParamField>

        <ParamField body="label" type="string | string[]" optional>
            Resume workflows with this label. Pass an array to match runs that
            have any of the given labels (OR semantics).
        </ParamField>

        <ParamField body="fromDate" type="Date | number" optional>
            Resume workflows created after this date.
        </ParamField>

        <ParamField body="toDate" type="Date | number" optional>
            Resume workflows created before this date.
        </ParamField>

        <ParamField body="callerIp" type="string" optional>
            Filter by the IP address that triggered the workflow.
        </ParamField>

        <ParamField body="flowControlKey" type="string" optional>
            Filter by flow control key.
        </ParamField>

        <ParamField body="workflowCreatedAt" type="number" optional>
            Filter by workflow creation time (Unix timestamp in ms).
        </ParamField>

        <ParamField body="failureFunctionState" type="string" optional>
            Filter by failure callback state.
        </ParamField>
    </Expandable>
</ParamField>

<ParamField body="count" type="number" optional>
    Maximum number of messages to process per call. Defaults to `100`.
</ParamField>

<ParamField body="cursor" type="string" optional>
    A pagination cursor from a previous request.
</ParamField>

### Resume all

<ParamField body="all" type="boolean">
    Set to `true` to resume all DLQ entries.
</ParamField>

### Options (second argument)

<ParamField body="flowControl" type="object" optional>
    An optional flow control configuration to limit concurrency and execution rate
    of resumed workflow runs.

    See [Flow Control](/docs/workflow/features/flow-control) for details.

    <Expandable title="properties">
        <ParamField body="key" type="string">
            A logical grouping key that identifies which resumed runs share the same flow control limits.
        </ParamField>

        <ParamField body="rate" type="number">
            The maximum number of allowed resumption requests per second.
        </ParamField>

        <ParamField body="parallelism" type="number">
            The maximum number of resumed runs that can execute concurrently.
        </ParamField>

        <ParamField body="period" type="string|number">
            The time window used to enforce the defined rate limit. Default is `1s`.
        </ParamField>
    </Expandable>
</ParamField>

<ParamField body="retries" type="number" optional>
    Number of retry attempts to apply when resuming the workflow run.
    Defaults to `3` if not provided.
</ParamField>

## Response

<ResponseField name="cursor" type="string">
    A pagination cursor. If not returned, all matching entries have been processed.
</ResponseField>

<ResponseField name="workflowRuns" type="object[]">
    <Expandable defaultOpen>
        <ResponseField name="workflowRunId" type="string">
            The ID of the workflow run resumed from the DLQ message.
        </ResponseField>

        <ResponseField name="workflowCreatedAt" type="number">
            The Unix timestamp (in milliseconds) when the resumed run was created.
        </ResponseField>
    </Expandable>
</ResponseField>

## Usage

<CodeGroup>
```ts Single
const { messages } = await client.dlq.list();

const response = await client.dlq.resume(messages[0].dlqId, {
  flowControl: {
    key: "my-flow-control-key",
    parallelism: 10,
  },
  retries: 3,
});
```
```ts Multiple
const response = await client.dlq.resume(["dlq-12345", "dlq-67890"]);
```
```ts By filters
let cursor: string | undefined;
do {
  const result = await client.dlq.resume({
    filter: {
      label: "my-label",
      workflowUrl: "https://example.com/workflow",
    },
    cursor,
  });
  cursor = result.cursor;
} while (cursor);
```
```ts All
let cursor: string | undefined;
do {
  const result = await client.dlq.resume({ all: true, cursor });
  cursor = result.cursor;
} while (cursor);
```
</CodeGroup>

# client.logs
Source: https://upstash.com/docs/workflow/basics/client/logs

The `logs` method retrieves workflow run logs using the [List Workflow Runs API](/docs/workflow/api-reference/logs/list-workflow-run-logs).

## Arguments

<ParamField body="cursor" type="string" optional>
    A pagination cursor from a previous request.
    Use this to fetch the next set of results.
</ParamField>

<ParamField body="count" type="number" optional>
    Maximum number of runs to return. Defaults to a system-defined value if not specified.
</ParamField>

<ParamField body="filter" type="object" optional>
    Filter options for narrowing down workflow run logs.

    <Expandable defaultOpen>
        <ParamField body="workflowRunId" type="string" optional>
            Filter by a specific workflow run ID.
        </ParamField>

        <ParamField body="state" type="string" optional>
            Filter workflow runs by execution state.

            | State        | Description                                |
            |--------------|--------------------------------------------|
            | `RUN_STARTED`  | The workflow run is in progress.          |
            | `RUN_SUCCESS`  | The workflow run completed successfully. |
            | `RUN_FAILED`   | The run failed after all retries.        |
            | `RUN_CANCELED` | The run was manually canceled.           |
        </ParamField>

        <ParamField body="workflowUrl" type="string" optional>
            Filter by the exact workflow URL.
        </ParamField>

        <ParamField body="label" type="string | string[]" optional>
            Filter by workflow label. Pass an array to match runs that have any
            of the given labels (OR semantics).
        </ParamField>

        <ParamField body="workflowCreatedAt" type="number" optional>
            Filter by the workflow creation time (Unix timestamp).
        </ParamField>

        <ParamField body="messageId" type="string" optional>
            Filter by a specific message ID.
        </ParamField>

        <ParamField body="fromDate" type="Date | number" optional>
            Filter workflow runs created after this date.
        </ParamField>

        <ParamField body="toDate" type="Date | number" optional>
            Filter workflow runs created before this date.
        </ParamField>

        <ParamField body="callerIp" type="string" optional>
            Filter by the IP address that triggered the workflow.
        </ParamField>

        <ParamField body="flowControlKey" type="string" optional>
            Filter by flow control key.
        </ParamField>
    </Expandable>
</ParamField>

## Response

<ResponseField name="cursor" type="string">
    A cursor to use for pagination.
    If no cursor is returned, there are no more workflow runs.
</ResponseField>

<ResponseField name="runs" type="Array">
  <Expandable>
    <ResponseField name="workflowRunId" type="string">
        The ID of the workflow run.
    </ResponseField>

    <ResponseField name="workflowUrl" type="string">
        The URL address of the workflow endpoint.
    </ResponseField>

    <ResponseField name="workflowState" type="string">
      The current state of the workflow run at this point in time

      | Value          | Description                                                    |
      | -------------- | -------------------------------------------------------------- |
      | `RUN_STARTED`  | The workflow has started to run and currently in progress.     |
      | `RUN_SUCCESS`  | The workflow run has completed succesfully.                    |
      | `RUN_FAILED`   | Some errors has occured and workflow failed after all retries. |
      | `RUN_CANCELED` | The workflow run has canceled upon user request.               |

    </ResponseField>

    <ResponseField name="workflowCreatedAt" type="number">
      The Unix timestamp (in milliseconds) when the workflow run started.
    </ResponseField>

    <ResponseField name="workflowRunCompletedAt" type="string">
        The Unix timestamp (in milliseconds) when the workflow run was completed, if applicable.
    </ResponseField>

    <ResponseField name="labels" type="string[]">
    The labels of the run assigned by the user on trigger.
    </ResponseField>

    <ResponseField name="label" type="string" deprecated>
    Deprecated. Use `labels` instead. When a run has multiple labels, this only
    contains the first one.
    </ResponseField>

    <ResponseField name="failureFunction" type="FailureFunction">
      The details of the failure callback message, if a failure function was defined for the workflow.

      <Expandable  defaultClosed>
        <ResponseField name="messageId" type="string">
          The ID of the failure callback message
        </ResponseField>

        <ResponseField name="url" type="string">
          The URL address of the failure function
        </ResponseField>

        <ResponseField name="state" type="string">
          The state of the failure callback

          | Value                 |
          | --------------------- |
          | `CALLBACK_INPROGRESS` |
          | `CALLBACK_SUCCESS`    |
          | `CALLBACK_FAIL`       |

        </ResponseField>

        <ResponseField name="failHeaders" type="string">
          The HTTP headers of the message that triggered the failure function.
        </ResponseField>

        <ResponseField name="failStatus" type="string">
          The HTTP response status of the message that triggered the failure function.
        </ResponseField>

        <ResponseField name="failResponse" type="string">
          The response body of the message that triggered the failure function.
        </ResponseField>

        <ResponseField name="dlqId" type="string">
          The DLQ ID of the workflow run.
        </ResponseField>

        <ResponseField name="responseBody" type="string">
          Response body of the failure function/url.
          When [failure function](/docs/workflow/basics/serve#failurefunction) is used, this contains
          the returned message from the failure function.
        </ResponseField>

        <ResponseField name="responseHeaders" type="array">
          Reponse headers of the failure function/url. This is valuable when the call to run the failure function/url is rejected
          because of a platform limit.
        </ResponseField>

        <ResponseField name="responseStatus" type="int">
          Reponse status of the failure function/url. This is valuable when the call to run the failure function/url is rejected
          because of a platform limit.
        </ResponseField>

        <ResponseField name="errors" type="array">
          A call to failure url/function can be retried as `maxRetries` time. This array contains errors of all retry
          attempts.

          <Expandable>
            <ResponseField name="status" type="int">
              Response status of the endpoint that caused the error
            </ResponseField>

            <ResponseField name="headers" type="array">
              Response Headers of the endpoint that caused the error
            </ResponseField>

            <ResponseField name="body" type="string">
              Response Body of the endpoint that caused the error if available
            </ResponseField>

            <ResponseField name="error" type="string">
              An error message that happened before/after calling the user's endpoint.
            </ResponseField>

            <ResponseField name="time" type="int64">
                The time of the error happened in Unix time milliseconds
            </ResponseField>

          </Expandable>
        </ResponseField>

        <ResponseField name="maxRetries" type="string">
          Max number of retries configured when seeing an error.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="steps" type="Array">

      <Expandable  defaultOpen>
        <ResponseField name="type" type="string" >

          The type of grouped steps

          | Value        | Description                                                      |
          | ------------ | ---------------------------------------------------------------- |
          | `sequential` | Indicates only one step is excuted sequentially                  |
          | `parallel`   | Indicates multiple steps being executed in parallel.             |
          | `next`       | Indicates there is information about currently executing step(s) |

        </ResponseField>

        <ResponseField name="steps" type="Array">
          <Expandable  defaultClosed>
            <ResponseField name="stepId" type="number" >
              The ID of the step which increases monotonically.
            </ResponseField>

            <ResponseField name="stepName" type="string" >
              The name of the step. It is specified in workflow by user.
            </ResponseField>

            <ResponseField name="stepType" type="string" >
                Execution type of the step which indicates type of the context function.

                | Value        | Function                                     |
                | ------------ | -------------------------------------------- |
                | `Initial`    | The default step which created automatically |
                | `Run`        | context.run()                                |
                | `Call`       | context.call()                               |
                | `SleepFor`   | context.sleepFor()                           |
                | `SleepUntil` | context.sleepUntil()                         |
                | `Wait`       | context.waitForEvent()                       |
                | `Notify`     | context.notify()                             |
                | `Invoke`     | context.invoke()                             |
            </ResponseField>

            <ResponseField name="messageId" type="string" >
                The ID of the message associated with this step.
            </ResponseField>

            <ResponseField name="out" type="string" >
                The output returned by the step
            </ResponseField>

            <ResponseField name="concurrent" type="string" >
                The total number of concurrent steps that is running alongside this step
            </ResponseField>

            <ResponseField name="state" type="string" >
                The state of this step at this point in time

                | Value           |
                | --------------- |
                | `STEP_SUCCESS`  |
                | `STEP_RETRY`    |
                | `STEP_FAILED`   |
                | `STEP_CANCELED` |
            </ResponseField>

            <ResponseField name="createdAt" type="string" >
              The unix timestamp in milliseconds when the message associated with this step has created.
            </ResponseField>

            <ResponseField name="nextDeliveryTime" type="number">
              The unix timestamp in milliseconds when this step will be retried.
              This is set only when the step state is `STEP_RETRY`
            </ResponseField>

            **The following fields are set only when a specific type of step is executing. These fields are not available for all step types.**

            <ResponseField name="sleepFor" type="string" >
              The duration in milliseconds which step will sleep. Only set if stepType is `SleepFor`.
            </ResponseField>

            <ResponseField name="sleepUntil" type="string" >
              The unix timestamp (in milliseconds) which step will sleep until. Only set if stepType is `SleepUntil`.
            </ResponseField>

            <ResponseField name="waitEventId" type="string" >
              The event id of the wait step. Only set if stepType is `Wait`.
            </ResponseField>

            <ResponseField name="waitTimeoutDeadline" type="string" >
              The unix timestamp (in milliseconds) when the wait will time out.
            </ResponseField>

            <ResponseField name="waitTimeoutDuration" type="string">
              The duration of timeout in human readable format (e.g. 120s, 1m, 1h).
            </ResponseField>

            <ResponseField name="waitTimeout" type="string" >
              Set to true if this step is cause of a wait timeout rather than notifying the waiter.
            </ResponseField>

            <ResponseField name="callUrl" type="string">
                The URL of the external address. Available only if stepType is `Call`.
            </ResponseField>

            <ResponseField name="callMethod" type="string">
                The HTTP method of the request sent to the external address. Available only if stepType is `Call`.
            </ResponseField>

            <ResponseField name="callHeaders" type="string">
                The HTTP headers of the request sent to the external address. Available only if stepType is `Call`.
            </ResponseField>

            <ResponseField name="callBody" type="string">
                The body of the request sent to the external address. Available only if stepType is `Call`.
            </ResponseField>

            <ResponseField name="callResponseStatus" type="number">
                The HTTP status returned by the external call. Available only if stepType is `Call`.
            </ResponseField>

            <ResponseField name="callResponseBody" type="string">
                The body returned by the external call. Available only if stepType is `Call`.
            </ResponseField>

            <ResponseField name="callResponseHeaders" type="array">
                The HTTP headers returned by the external call. Available only if stepType is `Call`.
            </ResponseField>

            <ResponseField name="invokedWorkflowRunId" type="string">
              The ID of the invoked workflow run if this step is an invoke step.
            </ResponseField>

            <ResponseField name="invokedWorkflowUrl" type="string">
              The URL address of the workflow server of invoked workflow run if this step is an invoke step.
            </ResponseField>

            <ResponseField name="invokedWorkflowCreatedAt" type="number">
              The Unix timestamp (in milliseconds) when the invoked workflow was started if this step is an invoke step.
            </ResponseField>

            <ResponseField name="invokedWorkflowRunBody" type="string">
              The body passed to the invoked workflow if this step is an invoke step.
            </ResponseField>

            <ResponseField name="invokedWorkflowRunHeaders" type="string">
              The HTTP headers passed to invoked workflow if this step is an invoke step.
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

***

## Usage

```ts
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" })

const { runs, cursor } = await client.logs()
```

### Paginate with cursor

```ts
const allRuns = [];
let cursor: string | undefined;
do {
  const result = await client.logs({ cursor });
  allRuns.push(...result.runs);
  cursor = result.cursor;
} while (cursor);
```

### Filter by state

```ts
const { runs } = await client.logs({
  filter: {
    state: "RUN_FAILED",
  }
})
```

### Filter by label and date range

```ts
const { runs } = await client.logs({
  filter: {
    label: "my-workflow",
    fromDate: new Date("2024-01-01"),
    toDate: new Date("2024-06-01"),
  }
})
```

### Filter by multiple labels

Passing an array matches runs that have any of the given labels (OR semantics).
For example, with runs labelled `["label-1", "label-2"]` and `["label-2", "label-3"]`,
filtering by `["label-1", "label-2"]` returns both.

```ts
const { runs } = await client.logs({
  filter: {
    label: ["label-1", "label-2"],
  }
})
```

# client.notify
Source: https://upstash.com/docs/workflow/basics/client/notify

The `notify` method notifies workflows that are waiting for a specific event.

Workflows paused at a [`context.waitForEvent`](/docs/workflow/basics/context/waitForEvent) step with the matching `eventId` will be resumed, and the provided `eventData` will be passed back to them.

## Arguments

<ParamField body="eventId" type="string" required>
    The identifier of the event to notify.
</ParamField>

<ParamField body="eventData" type="any" optional>
    Data to deliver to the waiting workflow(s).
    This value will be returned in the `eventData` field of `context.waitForEvent`.
</ParamField>

<ParamField body="workflowRunId" type="string" optional>
    The workflow run ID to notify. When provided, enables **lookback functionality** - the notification will be stored and delivered even if `notify` is called before `waitForEvent`.

    This solves race conditions where notifications might be sent before a workflow reaches its wait step.
</ParamField>

## Response

Returns a list of `Waiter` objects representing the workflows that were notified:

<ResponseField name="Waiter" type="object">
  <Expandable>
   	<ResponseField name="url" type="string" required>
      URL to call upon notify
    </ResponseField>
   	<ResponseField name="deadline" type="number" required>
      Unix timestamp for when the wait will time out
    </ResponseField>
   	<ResponseField name="headers" type="Record<string, string[]>" required>
      Headers sent in case of notify
    </ResponseField>
   	<ResponseField name="timeoutUrl" type="string">
      URL to call upon timeout
    </ResponseField>
   	<ResponseField name="timeoutBody" type="unknown">
      Body used in timeout request
    </ResponseField>
   	<ResponseField name="timeoutHeaders" type="Record<string, string[]>">
      Headers sent in case of time out
    </ResponseField>
  </Expandable>
</ResponseField>

## Usage

### Basic Notification

```javascript
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" });

await client.notify({
  eventId: "my-event-id",
  eventData: "my-data", // data passed to the workflow run
});
```

### Notification with Lookback

To prevent race conditions where a notification might arrive before the workflow reaches `waitForEvent`, you can provide a `workflowRunId`. This enables lookback - the notification will be stored and delivered even if sent before the wait step:

```javascript
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" });

// Notify a specific workflow run with lookback support
await client.notify({
  eventId: "payment-processed",
  eventData: { amount: 100, status: "success" },
  workflowRunId: "wfr_abc123", // Enables lookback for this workflow run
});
```

This is particularly useful when you know which specific workflow run should receive the notification, such as after triggering a workflow and immediately sending it an event.

# client.trigger
Source: https://upstash.com/docs/workflow/basics/client/trigger

The `trigger` method starts a new workflow run and returns its `workflowRunId`.

You can also trigger multiple workflow runs in a single call by passing an array of arguments instead of a single object.

## Arguments

<ParamField body="url" type="string" required>
    The public URL of the workflow endpoint.
</ParamField>

<ParamField body="workflowRunId" type="string" optional>
    A custom identifier for the workflow run.
    Each run must use a unique ID.

    The final ID will be prefixed with `wfr_`.
    For example: passing `my-workflow` results in `wfr_my-workflow`.

    If omitted, a run ID will be generated automatically.
</ParamField>

<ParamField body="body" type="string | object" optional>
    The request payload to pass into the workflow run.
    Accessible as `context.requestPayload` inside the workflow.
</ParamField>

<ParamField body="headers" type="object" optional>
    HTTP headers to pass into the workflow run.
    Accessible as `context.headers` inside the workflow.
</ParamField>

<ParamField body="retries" type="string">
    Number of retry attempts for workflow steps. Default is 3.
</ParamField>

<ParamField body="retryDelay" type="string">
    Delay between retries. Can use expressions like "1000 * (1 + retried)".
</ParamField>

<ParamField body="flowControl" type="object" optional>
    An optional flow control configuration to limit concurrency and execution rate of the workflow runs.

    See [Flow Control](/docs/workflow/features/flow-control) for details.

    <Expandable title="properties">
        <ParamField body="key" type="string">
          A logical grouping key that identifies which requests share the same flow control limits.
        </ParamField>

        <ParamField body="rate" type="number">
            The maximum number of allowed requests per second.
        </ParamField>

        <ParamField body="parallelism" type="number">
            The maximum number of concurrent requests allowed.
        </ParamField>

        <ParamField body="period" type="string|number">
            The time window used to enforce the defined rate limit. Default is `1s`.
        </ParamField>
    </Expandable>
</ParamField>

<ParamField body="delay" type="string">
    Delay for the workflow run. This is used to delay the execution of the workflow run. The delay is in seconds or can be passed as a string with a time unit (e.g. "1h", "30m", "15s").
</ParamField>

<ParamField body="notBefore" type="number">
  Optionally set the absolute delay of this message.
  This will override the delay option.
  The message will not delivered until the specified time.

  Unix timestamp in seconds.
</ParamField>

<ParamField body="label" type="string | string[]">
  An optional label to assign to the workflow run.
  This can be useful for identifying and filtering runs in the dashboard or logs.

  Pass an array to attach multiple labels to a single workflow run. The run will
  then match a [logs](/docs/workflow/basics/client/logs) or DLQ filter for any of its
  labels (OR semantics).
</ParamField>

<ParamField body="disableTelemetry" type="boolean">
  If set to true, telemetry data collection for this workflow run will be disabled.
  By default, telemetry is enabled to help improve Upstash services.

  See the [`disableTelemetry` parameter in serve options](/docs/workflow/basics/serve/advanced#param-disable-telemetry) for more details.
</ParamField>

## Usage
<CodeGroup>
```ts Single Workflow
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" })

const { workflowRunId } = await client.trigger({
  url: "https://<YOUR_WORKFLOW_ENDPOINT>/<YOUR-WORKFLOW-ROUTE>",
  body: "hello there!",         // optional body
  headers: { ... },             // optional headers
  workflowRunId: "my-workflow", // optional workflow run id
  retries: 3,                   // optional retries
  retryDelay: "1000 * (1 + retried)", // optional delay between retries
  delay: "10s"                  // optional delay value
  failureUrl: "https://<YOUR_FAILURE_URL>", // optional failure url
  flowControl: {                // optional flow control
    key: "USER_GIVEN_KEY",
    rate: 10,
    parallelism: 5,
    period: "10m"
  },
  label: ["team-a", "high-priority"], // optional label(s); single string or array
})
```

```ts Multiple Workflows
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" })
const results = await client.trigger([
  {
    url: "<YOUR_WORKFLOW_ENDPOINT>",
    // other options...
  },
  {
    url: "<YOUR_WORKFLOW_ENDPOINT>",
    // other options...
  },
])

console.log(results[0].workflowRunId)
// prints wfr_my-workflow
```
</CodeGroup>

# client.getWaiters
Source: https://upstash.com/docs/workflow/basics/client/waiters

The `getWaiters` method retrieves all waiters that are currently listening for a given event.

A **waiter** represents a workflow run that is paused at a `context.waitForEvent` step and is waiting for the specified `eventId`.

## Arguments

<ParamField body="eventId" type="string" required>
    The identifier of the event to look up.
</ParamField>

## Response

Returns a list of `Waiter` objects describing workflows that are waiting on the given event.

<ResponseField name="Waiter" type="object">
  <Expandable>
   	<ResponseField name="url" type="string" required>
      URL to call upon notify
    </ResponseField>
   	<ResponseField name="deadline" type="number" required>
      Unix timestamp for when the wait will time out
    </ResponseField>
   	<ResponseField name="headers" type="Record<string, string[]>" required>
      Headers sent in case of notify
    </ResponseField>
   	<ResponseField name="timeoutUrl" type="string">
      URL to call upon timeout
    </ResponseField>
   	<ResponseField name="timeoutBody" type="unknown">
      Body used in timeout request
    </ResponseField>
   	<ResponseField name="timeoutHeaders" type="Record<string, string[]>">
      Headers sent in case of time out
    </ResponseField>
  </Expandable>
</ResponseField>

## Usage

```javascript
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" });

const result = await client.getWaiters({
  eventId: "my-event-id",
});
```

# How Workflow Works
Source: https://upstash.com/docs/workflow/basics/how

Upstash Workflow is an orchestration layer that allows you to write **multi‑step workflows** which are:

* **Durable** – steps automatically recover from errors or outages
* **Scalable** – steps run independently and in parallel when possible
* **Cost‑efficient** – idle waiting (delays, sleeps, external calls) does not consume compute resources

Upstash Workflow is built on top of Upstash QStash, our serverless messaging and scheduling solution, to achieve these features.

## The Core Idea

Traditionally, backend functions are built in one of two ways: either everything is executed inside a single API function—which is difficult to maintain and prone to failures—or the flow is split across multiple APIs connected by a queueing system, which adds significant infrastructure and state‑management overhead.

These approaches can work, but they often fail to handle production load reliably or become increasingly difficult to maintain over time:

* **Timeouts** – the whole function runs inside one execution window. A slow API can easily exceed serverless limits (often 10–60 seconds).
* **Temporary issues** – slow or unreliable external services can exceed serverless limits or cause the entire request to fail.
* **Failures** – if a step fails, the whole request fails. You either restart everything or you must write custom retry logic.
* **Rate limits** – calling external APIs in bulk requires careful concurrency control, which is difficult to implement manually.
* **Complexity** – to address these issues, teams often build custom queues, schedulers, or state trackers, adding unnecessary infrastructure overhead.

***

## How Upstash Workflow Solves This

Upstash Workflow takes a different approach:
instead of treating your entire function as one continuous execution, **it splits your logic into multiple steps in a workflow endpoint**, each managed and retried by the orchestration engine.

* Each step is executed in its own **HTTP call** to your application.
* After a step finishes, its result is **stored in durable state** inside Upstash Workflow.
* On the next execution, Workflow **skips completed steps** and **resumes exactly where it left off by restoring the previous step results**.
* If a step fails, it is retried automatically based on your retry configuration.

This means you no longer need custom queues, retry logic, or manual state management. You just define your workflow once, and the orchestration layer ensures that **every step runs once, in order, with full reliability.**

  <img />

***

## Extended Features

Upstash Workflow extends the basic step model with additional primitives:

* **Parallel Steps**
  Define multiple steps (e.g. inside a `Promise.all()`). The engine detects independent work and runs steps concurrently as separate HTTP executions.

* **Delays / Sleep**
  `context.sleep` and `context.sleepUntil` allow pausing a workflow for hours, days, or even months. No compute is held during the wait time; execution resumes when the delay has expired.

* **External Event Handling**
  `context.waitForEvent` pauses execution until you notify the workflow externally (e.g. via webhook or user action). State is persisted until the event arrives.

* **External Calls**
  Use `context.call` to have Upstash perform slow or unreliable HTTP calls. Instead of blocking your function, the call is handled by Upstash. When it completes, the workflow resumes with the response.

***

This architecture makes your serverless functions durable, reliable, and performance‑optimized, even in the face of runtime errors or temporary service outages.

It's quick and easy to get started: follow the [Quickstarts](/docs/workflow/quickstarts/platforms) to define your first workflow in minutes.

# Overview
Source: https://upstash.com/docs/workflow/basics/serve

Use the `serve()` function to define an endpoint that runs a workflow.
It accepts two arguments:

1. **Route Function**: an async function that receives the workflow context and defines the workflow steps.
2. **Options**: configuration options for the workflow.

<CodeGroup>
    ```typescript TypeScript
    import { serve } from "@upstash/workflow/nextjs";

    export const { POST } = serve(async (context) => {
      // Route function
    }, {
      // Options
    });
    ```

    ```python Python
    from fastapi import FastAPI
    from upstash_workflow.fastapi import Serve
    from upstash_workflow import AsyncWorkflowContext

    app = FastAPI()
    serve = Serve(app)

    @serve.post("/api/example")
    async def example(context: AsyncWorkflowContext[str]) -> None:
        async def _step1() -> str:
            # define a piece of business logic as step 1
            return "step 1 result"

        result = await context.run("step-1", _step1)

        async def _step2() -> None:
            # define another piece of business logic as step 2
            pass

        await context.run("step-2", _step2)
    ```
</CodeGroup>

## Route Function

The route function defines the execution logic of the workflow.
It is an async function that receives a context object, which is automatically created and passed by Upstash Workflow.

The context object provides:
* **Workflow APIs** – functions for defining workflow steps.
* **Workflow Run Properties** – request payload, request headers, and other metadata.

For a full list of available APIs and properties, see the [Workflow Context](/docs/workflow/basics/context) documentation.

<CodeGroup>
    ```typescript TypeScript highlight={4-9}
    import { serve } from "@upstash/workflow/nextjs";

    export const { POST } = serve(
      async (context) => {
        // 👇 Access context properties
        const { userId } = context.requestPayload;
        // 👇 Define a workflow step
        await context.run("step-1", async () => {})
      }
    );
    ```

    ```python Python
    from fastapi import FastAPI
    from upstash_workflow.fastapi import Serve
    from upstash_workflow import AsyncWorkflowContext

    app = FastAPI()
    serve = Serve(app)

    @serve.post("/api/example")
    async def example(context: AsyncWorkflowContext[str]) -> None:
        async def _step1() -> str:
            # define a piece of business logic as step 1
            return "step 1 result"

        result = await context.run("step-1", _step1)

        async def _step2() -> None:
            # define another piece of business logic as step 2
            pass

        await context.run("step-2", _step2)

    ```
</CodeGroup>

## Options

Options provide additional configuration for workflow runs.
Most of them are advanced settings and are not required for typical use cases. See [Advanced Options](/docs/workflow/basics/serve/advanced) for more details.

<CodeGroup>

    ```typescript TypeScript highlight={5-8}
    import { serve } from "@upstash/workflow/nextjs";

    export const { POST } = serve(
        async (context) => { ... },
        // 👇 Workflow options
        {
            failureFunction: async ({ ... }) => {}
        }
    );
    ```

    ```python Python
    from fastapi import FastAPI
    from upstash_workflow.fastapi import Serve
    from upstash_workflow import AsyncWorkflowContext

    app = FastAPI()
    serve = Serve(app)

    @serve.post("/api/example")
    async def example(context: AsyncWorkflowContext[str]) -> None:
        async def _step1() -> str:
            # define a piece of business logic as step 1
            return "step 1 result"

        result = await context.run("step-1", _step1)

        async def _step2() -> None:
            # define another piece of business logic as step 2
            pass

        await context.run("step-2", _step2)

    ```
</CodeGroup>

# Advanced Options
Source: https://upstash.com/docs/workflow/basics/serve/advanced

Advanced Options are intended to support edge cases or testing pipelines and are **not required for regular use**.

<ParamField path="failureFunction" type="string">
    Defines a function that executes if the workflow fails after all retries are exhausted.

    For details, see [failureFunction](/docs/workflow/features/failure-callback).

    <CodeGroup>
        ```typescript TypeScript
        export const { POST } = serve<string>(
          async (context) => { ... },
          {
            failureFunction: async ({
              context,      // context during failure
              failStatus,   // failure status
              failResponse, // failure message
              failHeaders,  // failure headers
              failStack     // failure stack trace (if available)
            }) => {
              // handle the failure
            }
          }
        );
        ```

        ```python Python
        async def failure_function(
          context,       # context during failure
          fail_status,   # failure status
          fail_response, # failure message
          fail_headers   # failure headers
        ):
          # handle the failure
          pass

        @serve.post("/api/example", failure_function=failure_function)
        async def example(context: AsyncWorkflowContext[str]) -> None: ...
        ```

    </CodeGroup>
</ParamField>

<ParamField path="failureUrl" type="string">

    <Note>
        This parameter is only available in Python SDK. In Javascript SDK, you can pass this value when triggering the workflow.
    </Note>

    The `failureUrl` option defines an external endpoint that will be called if the workflow fails after all retries are exhausted.

    This option is an advanced alternative to `failureFunction`.
    For more details, see [Advanced failureUrl Option](/docs/workflow/features/failureFunction/advanced).

    <CodeGroup>

    ```python Python
    @serve.post("/api/example", failureUrl="https://<YOUR-FAILURE-ENDPOINT>/...")
    async def example(context: AsyncWorkflowContext[str]) -> None: ...
    ```

    </CodeGroup>
</ParamField>

<ParamField path="retries" type="number">

    <Note>
        This parameter is only available in Python SDK. In Javascript SDK, you can pass this value when triggering the workflow.
    </Note>

    Defines the number of retry attempts if a workflow step fails.
    The default value is 3.

    For details, see [retry configuration](/docs/workflow/features/retries#configuration).

    <CodeGroup>

        ```python Python
        @serve.post("/api/example", retries=3)
        async def example(context: AsyncWorkflowContext[str]) -> None: ...
        ```
    </CodeGroup>
</ParamField>

<ParamField path="middlewares" type="WorkflowMiddleware[]">
    An array of middleware instances that intercept workflow lifecycle and debug events.

    Middlewares allow you to hook into various stages of workflow execution (before/after steps, run start/completion)
    and debug events (errors, warnings, info logs).

    For details and examples, see [Middlewares](/docs/workflow/howto/middlewares).

    <CodeGroup>

    ```typescript TypeScript
    import { serve } from "@upstash/workflow/nextjs";
    import { loggingMiddleware } from "@upstash/workflow";

    export const { POST } = serve<string>(
      async (context) => { ... },
      {
        middlewares: [loggingMiddleware]
      }
    );
    ```

    </CodeGroup>
</ParamField>

<ParamField path="initialPayloadParser" type="bool">
    Enables custom parsing of the initial request payload.

    Use this option if the incoming payload is not plain JSON or a simple string.
    The parser function lets you transform the raw request into a strongly typed
    object before workflow execution begins.

    <CodeGroup>

    ```typescript TypeScript
    type InitialPayload = {
      foo: string;
      bar: number;
    };

    // 👇 1: provide initial payload type
    export const { POST } = serve<InitialPayload>(
      async (context) => {
        // 👇 3: parsing result is available as requestPayload
        const payload: InitialPayload = context.requestPayload;
      },
      {
        // 👇 2: custom parsing for initial payload
        initialPayloadParser: (initialPayload) => {
          const payload: InitialPayload = parsePayload(initialPayload);
          return payload;
        },
      }
    );
    ```

    ```python Python
    @dataclass
    class InitialPayload:
        foo: str
        bar: int

    def initial_payload_parser(initial_payload: str) -> InitialPayload:
        return parse_payload(initial_payload)

    @serve.post("/api/example", initial_payload_parser=initial_payload_parser)
    async def example(context: AsyncWorkflowContext[InitialPayload]) -> None:
        payload: InitialPayload = context.request_payload

    ```

    </CodeGroup>

</ParamField>

<ParamField path="schema" type="z.ZodType">
    Alternative to `initialPayloadParser`, you can pass a `schema` in the TypeScript SDK.

    The schema is used to validate and parse the initial request payload automatically using [Zod](https://zod.dev/).

    <CodeGroup>

    ```typescript TypeScript

    import { z } from "zod";

    const parameters = z.object({ expression: z.string() });

    export const { POST } = serve(
      async (context) => {
        // context.requestPayload is typed as `{ expression: string }`
        const payload = context.requestPayload;
      },
      {
        schema: parameters,
      }
    );
    ```

    </CodeGroup>
</ParamField>

<ParamField path="url" type="string">
    Specifies the full endpoint URL of the workflow, including the route path.

    By default, Upstash Workflow infers the URL from `request.url` when scheduling the next step.
    However, in some environments, `request.url` may resolve to an internal or unreachable address.

    Use this option when running behind a proxy, reverse proxy, or local tunnel during development where `request.url` cannot be used directly.

    <CodeGroup>

        ```typescript TypeScript
        export const { POST } = serve<string>(
          async (context) => { ... },
          {
            url: "https://<YOUR-DEPLOYED-APP>.com/api/workflow"
          }
        );
        ```

        ```python Python
        @serve.post("/api/example", url="https://<YOUR-DEPLOYED-APP>.com/api/workflow")
        async def example(context: AsyncWorkflowContext[str]) -> None: ...
        ```
    </CodeGroup>
</ParamField>

<ParamField path="baseUrl" type="string">

    Similar to `url`, but `baseUrl` only overrides the base portion of the inferred URL rather than replacing the entire path.
    This is useful when you want to preserve the route structure while changing only the host or scheme.

    <Tip>
        If you have multiple workflow endpoints, you can set the `UPSTASH_WORKFLOW_URL` environment variable instead of configuring `baseUrl` on each endpoint.
        The `UPSTASH_WORKFLOW_URL` environment variable corresponds directly to this option and configures it globally.
    </Tip>

    <CodeGroup>

        ```typescript TypeScript
        export const { POST } = serve<string>(
          async (context) => {
            ...
          },
          // options:
          {
            baseUrl: "<LOCAL-TUNNEL-PUBLIC-URL>"
          }
        );
        ```

        ```python Python
        @serve.post("/api/example", base_url="<LOCAL-TUNNEL-PUBLIC-URL>")
        async def example(context: AsyncWorkflowContext[str]) -> None: ...

        ```

    </CodeGroup>
</ParamField>

<ParamField path="qstashClient" type="object">

    Use `qstashClient` if you want to provide your own QStash client instead of letting Workflow use the default from environment variables.

    This is useful if you're working with multiple QStash projects in the same app.

    <CodeGroup>

        ```typescript TypeScript
        import { Client } from "@upstash/qstash";
        import { serve } from "@upstash/workflow/nextjs";

        export const { POST } = serve(
          async (context) => { ... },
          {
            qstashClient: new Client({ token: "<QSTASH_TOKEN>" })
          }
        );
        ```

        ```python Python
        from qstash import AsyncQStash

        @serve.post("/api/example", qstash_client=AsyncQStash(os.environ["QSTASH_TOKEN"]))
        async def example(context: AsyncWorkflowContext[str]) -> None: ...

        ```

    </CodeGroup>

</ParamField>

<ParamField path="receiver" type="object">

    The `Receiver` verifies that every request to your endpoint actually comes from QStash, blocking anyone else from triggering your workflow.

    The `receiver` option allows you to pass a QStash Receiver explicitly.

    By default, Workflow initializes the Receiver automatically using the environment variables `QSTASH_CURRENT_SIGNING_KEY` and `QSTASH_NEXT_SIGNING_KEY`.

    This is useful if you're working with multiple QStash projects in the same app.

    <CodeGroup>

        ```typescript TypeScript
        import { Receiver } from "@upstash/qstash";
        import { serve } from "@upstash/workflow/nextjs";

        export const { POST } = serve<string>(
          async (context) => { ... },
          {
            receiver: new Receiver({
              currentSigningKey: "<QSTASH_CURRENT_SIGNING_KEY>",
              nextSigningKey: "<QSTASH_NEXT_SIGNING_KEY>",
            })
          }
        );
        ```

        ```python Python
        from qstash import Receiver

        @serve.post(
            "/api/example",
            receiver=Receiver(
                current_signing_key=os.environ["QSTASH_CURRENT_SIGNING_KEY"],
                next_signing_key=os.environ["QSTASH_NEXT_SIGNING_KEY"],
            ),
        )
        async def example(context: AsyncWorkflowContext[str]) -> None:
            ...
        ```

    </CodeGroup>

</ParamField>

<ParamField path="env" type="object">

By default, Workflow uses `process.env` to read credentials and initialize QStash.
If you're in an environment where `process.env` isn't available, or you want to inject values manually, you can pass them with `env`.

Inside your workflow, these values are also exposed on `context.env`.

<CodeGroup>

```typescript TypeScript
import { Receiver } from "@upstash/qstash";
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve<string>(
  async (context) => {
    // the env option will be available in the env field of the context:
    const env = context.env;
  },
  {
    env: {
        QSTASH_URL: "<QSTASH_URL>",
        QSTASH_TOKEN: "<QSTASH_TOKEN>",
        QSTASH_CURRENT_SIGNING_KEY: "<QSTASH_CURRENT_SIGNING_KEY>",
        QSTASH_NEXT_SIGNING_KEY: "<QSTASH_NEXT_SIGNING_KEY>",
    }
  }
);
```

```python Python
@serve.post(
    "/api/example",
    env={
        "QSTASH_CURRENT_SIGNING_KEY": os.environ["QSTASH_CURRENT_SIGNING_KEY"],
        "QSTASH_NEXT_SIGNING_KEY": os.environ["QSTASH_NEXT_SIGNING_KEY"],
    },
)
async def example(context: AsyncWorkflowContext[str]) -> None:
    ...
```

</CodeGroup>

</ParamField>

<ParamField path="verbose" type="boolean">

    Enables verbose mode to print detailed logs of workflow execution to the application's `stdout`.

    Verbose mode is disabled by default.

    ```typescript
    export const { POST } = serve<string>(
      async (context) => { ... },
      {
        verbose: true
      }
    );
    ```

</ParamField>

<ParamField path="disableTelemetry" type="boolean">
    Disables anonymous telemetry data collection for this workflow endpoint. Since we don't collect telemetry
    in Python SDK, this option is only available in the TypeScript SDK.

    By default, the Upstash Workflow SDK collects anonymous telemetry data to help improve the service.
    The collected data includes:

    * SDK version
    * Platform (Vercel, AWS, etc.)
    * Runtime version (Node.js, Python, etc.)

    Set `disableTelemetry` to `true` to opt out of telemetry for this specific workflow endpoint.

    <CodeGroup>

        ```typescript TypeScript
        export const { POST } = serve<string>(
          async (context) => { ... },
          {
            disableTelemetry: true
          }
        );
        ```

        ```python Python
        @serve.post("/api/example", disable_telemetry=True)
        async def example(context: AsyncWorkflowContext[str]) -> None: ...
        ```
    </CodeGroup>

    <Tip>
        You should also
        set [`disableTelemetry` when triggering workflow runs via `client.trigger()`](/docs/workflow/basics/client/trigger#param-disable-telemetry) to fully disable telemetry
    </Tip>
</ParamField>

# Changelog
Source: https://upstash.com/docs/workflow/changelog

<Note>
  We have moved the roadmap and the changelog to [Github Discussions](https://github.com/orgs/upstash/discussions) starting from October 2025.Now you can follow `In Progress` features. You can see that your `Feature Requests` are recorded. You can vote for them and comment your specific use-cases to shape the feature to your needs.
</Note>

<Update label="June 2026">
* **TypeScript SDK (`workflow-js`):**
    * Multiple labels per workflow run are now supported. `label` on [`client.trigger`](/docs/workflow/basics/client/trigger) and `context.invoke` accepts `string | string[]`, and log/DLQ/cancel filters accept an array to match runs that have any of the given labels (OR semantics). Workflow run logs now expose a `labels: string[]` field, and `context.labels: string[]` replaces the now-deprecated `context.label`.
</Update>

<Update label="March 2026">
* **TypeScript SDK (`workflow-js`):**
    * Added optional `workflowRunId` parameter to `notify` method, enabling **lookback functionality**. When provided, notifications are stored and delivered even if sent before a workflow reaches `waitForEvent`, preventing race conditions. See [notify documentation](/docs/workflow/basics/client/notify) and [wait-for-event guide](/docs/workflow/features/wait-for-event#race-condition-between-wait-and-notify) for details.
</Update>

<Update label="September 2025">
* **TypeScript SDK (`workflow-js`):**
    * `Label` feature is added. This will enable our users to label their workflow runs so that
        * Logs can be filtered with user given label.
        * DLQ can be filtered with user given label.
    * `notBefore` parameter is added to `trigger` function that will allow starting a workflow run at a later date
    given by the `notBefore` parameter.
* **Console:**
    * A major Workflow redesign is landed to improve debugging and monitoring experience workflow runs logs.
        * `Flat view` is removed. All the data is moved to single view. This is also to avoid confusing our
        users and made over all experience simpler.
</Update>

<Update label="August 2025">
* **TypeScript SDK (`workflow-js`):**
    * Added `retryDelay` option to dynamicaly program the retry duration. It can be configured on
    [trigger](/docs/workflow/basics/client#trigger-workflow) , [context.call](/docs/workflow/basics/context#context-call)
    or [serve](/docs/workflow/basics/serve#retrydelay)
    * Added ability to detect if a given url is a workflow or not. Starting with `0.2.17` trigger made via the sdk can fail (instead of hanging),
    if there is no workflow serve on the given url.
* **Console:**
    * Local mode is added to enable our users to use the console with their local development envrionment and the locally deployed workflows.
    See [docs](/docs/workflow/howto/local-development#development-server-recommended) for details.
</Update>

<Update label="July 2025">
* **TypeScript SDK (`workflow-js`):**
    * Restart/Resume for DLQ is added to allow more options to handle failed runs. See [here](/docs/workflow/howto/failures#manually-handling-failed-workflow-runs)
    * Added `WorkflowNonRetryableError` to fail a workflow without causing any retries. See [here](/docs/workflow/basics/context#error-handling-and-retries)
    * For additional bug fixes, see the full changelog [here](https://github.com/upstash/workflow-js/compare/v0.2.14...v0.2.16).
</Update>

<Update label="June 2025">
* **TypeScript SDK (`workflow-js`):**
    * Added `useFailureFunction` and `failureFunction` to `client.trigger`. See [here](https://github.com/upstash/workflow-js/pull/107).
    * Added batch triggering support to `client.trigger`. See [here](https://github.com/upstash/workflow-js/pull/110).
    * For additional bug fixes, see the release notes [here](https://github.com/upstash/workflow-js/releases/tag/v0.2.14).
* **Python SDK (`workflow-py`):**  
    * Failure function is implemented. This feature enables to act on a failure of a workflow on the code. See docs [here](/docs/workflow/howto/failures#using-a-failurefunction-recommended)
    * For other bug fixes, see the full changelog [here](https://github.com/upstash/workflow-py/compare/v0.1.0...v0.1.1).
* **Console:**
    * A major redesign is coming next month to improve Workflow usability.
* **Workflow Server:**  
    * An issue causing Workflows not usable with `CloudFront` is fixed.
</Update>

<Update label="May 2025">
* **TypeScript SDK (`workflow-js`):**
    * Added a `workflow` parameter to `context.call`, enabling type-safe workflow calls. See [here](https://github.com/upstash/workflow-js/pull/75).
    * Enabled passing `context.call` settings when defining an Agent. See [here](https://github.com/upstash/workflow-js/pull/90).
    * Added `delay` support to `client.trigger`. See [here](https://github.com/upstash/workflow-js/pull/100).
    * Introduced `period` and improved `rate` support in flow control. See [here](https://github.com/upstash/workflow-js/pull/101).
      Previously, `period` was fixed at 1 second. For example, `rate: 3 period: 1d` throttles publishes to 3 per day.
    * For additional bug fixes, see the release notes [here](https://github.com/upstash/workflow-js/releases/tag/v0.2.13).
* **Workflow Server:**
    * Added support for custom `period` in flow control, allowing users to set a period of up to 1 week.
      Previously, `period` was fixed at 1 second. For example, `rate: 3 period: 1d` throttles publishes to 3 per day.
    * Implemented **Workflow Resume** and **Restart** features (SDK and Console support in progress):
        * **Resume** allows users to retry a workflow run from the point it stopped.
        * **Restart** allows users to retry a workflow run from the beginning.
* **Console:**
    * A major redesign is coming to improve Workflow usability.
</Update>

<Update label="April 2025">
* **Python SDK (`workflow-py`):**  
    * Minor bug fixes.
      See the full changelog [here](https://github.com/upstash/workflow-py/compare/v0.1.0...v0.1.1).
* **Workflow Server:**
    * Prevented intermediate Workflow calls from failing due to request/message quota limits.
    * Fixed handling of `RUN_STARTED` so that it correctly returns unfinished Workflow Runs as documented.
      Previously, some Workflow Runs could be skipped if internal state was logged after `RUN_STARTED`.
    * Applied several performance optimizations.
</Update>

<Update label="March 2025">
* **TypeScript SDK (`workflow-js`):**
	* Added `onError` support to `serve` by the community. See [here](https://github.com/upstash/workflow-js/pull/79).
    * Enabled support for all fetch-compatible models in Agents. See [more details here](https://github.com/upstash/workflow-js/pull/77).
    * For additional bug fixes, see the full changelog [here](https://github.com/upstash/workflow-js/compare/v0.2.11...v0.2.12).
</Update>

<Update label="February 2025">
* **TypeScript SDK (`workflow-js`):**
	* Fixed a Unicode issue in `context.call` where binary responses from endpoints could break. See [here](https://github.com/upstash/workflow-js/pull/71).
    * Introduced `WorkflowTool`, allowing Workflow Agents to define multi-step workflows as a tool. See [here](/docs/workflow/agents/features#tools).
    * Added `context.invoke` to call one workflow from another with full type-safety. See the guide [here](/docs/workflow/features/invoke).
    * Introduced flow control parameters to limit the rate or concurrency of workflow runs. Learn more [here](/docs/workflow/features/flow-control).
    * For additional bug fixes, see the full changelog [here](https://github.com/upstash/workflow-js/compare/v0.2.3...v0.2.6).
* **Workflow Server:**
    * Added RateLimit and Parallelism controls to manage the frequency and concurrency of workflow runs. Learn more [here](/docs/workflow/features/flow-control).
</Update>

<Update label="January 2025">
* **TypeScript SDK (`workflow-js`):**
    * Added the Agents API to workflows. You can now create AI agents to run workflows on your own infrastructure with all the benefits of workflows: reduced environment costs, fault tolerance, and scalability. Learn more about agents [here](/docs/workflow/agents/overview).
    * For other bug fixes, see the full changelog [here](https://github.com/upstash/workflow-js/compare/v0.2.3...v0.2.6).
* **Python SDK (workflow-py):**
    * Released [`workflow-py`](https://github.com/upstash/workflow-py).
* **Local Development Server:**
    * The local development server is now available for public use. This server allows you to test your workflows locally. Learn more about the local development server [here](/docs/workflow/howto/local-development#development-server-recommended).
* **Console:**
    * Separated Workflow and QStash consoles for a better user experience.
    * Separated their DLQ messages as well.
* **Workflow Server:**
    * The core team focused on RateLimit and Parallelism features. These features are ready on the server and will be announced next month after the documentation and SDKs are completed.
</Update>

<Update label="December 2024">
* **TypeScript SDK (`workflow-js`):**
    * Introduced third-party integrations, starting with Anthropic, Resend, and OpenAI. These integrations are automatically offloaded to workflows, ensuring long-running calls do not consume user environment time. See the related documentation [here](/docs/workflow/basics/context#context-api).
    * Added a `timeout` parameter to `context.call`. Learn more in the [documentation](/docs/workflow/basics/context#context-call).
    * Improved support for workflows in Express and SvelteKit by adding the `useJSONContent` option.
    * Resolved loop detection issues on Cloudflare and Render.
    * Full changelog, including all fixes, is available [here](https://github.com/upstash/workflow-js/compare/v0.2.0...v0.2.3).

* **Workflow Server:**
    * Added the `WorkflowCreatedAt` filter for Dead Letter Queue (DLQ) and Events.
    * Prepared the local development server for public release (coming soon).
    * Enhanced `context.SleepUntil` to support float values.
    * Increased the event retention period from 10,000 events to up to 14 days. Learn more on the [Pricing page](https://upstash.com/pricing/workflow).
</Update>

<Update label="November 2024">
* **Python SDK (workflow-py):**
    * Began development of the Python SDK.
* **TypeScript SDK (workflow-js):**
    * Added support for string durations (e.g., `1d`, `30s`) in `context.sleep` and `context.waitForEvent`.
    * Introduced integrations for [Astro](/docs/workflow/quickstarts/astro) and [Express](/docs/workflow/quickstarts/express).
    * Added `client.trigger`, enabling workflows to start and return the workflow run ID. See the [documentation](/docs/workflow/basics/client#trigger-workflow).
    * Added a retry option for `context.call`. See the [documentation](/docs/workflow/basics/context#context-call).
    * Introduced a lazy fetch feature to support longer and larger workflows on resource-limited platforms.
    * Added `context.cancel` to cancel the current workflow. See the [documentation](/docs/workflow/basics/context#context-cancel).
    * Full changelog, including fixes, is available [here](https://github.com/upstash/workflow-js/compare/v0.1.2...v0.2.0).
* **Workflow Server:**
    * Added bulk cancel functionality for workflow runs. See the [REST API](/docs/workflow/api-reference/runs/bulk-cancel-workflow-runs).
    * Introduced content-based deduplication for workflows and retry-until-success functionality. This will allow workflows to be used in areas with unstable network connection.
</Update>

<Update label="October 2024">
* Optimized the console by trimming event bodies, reducing resource usage and enabling efficient querying of events with large payloads.
* Began development on a new architecture to deliver faster event processing on the server.
* Added [Wait Notify](/docs/workflow/features/wait-for-event) feature.
</Update>

<Update label="September 2024">
* Bug fixes and internal logging improvements.
</Update>

<Update label="August 2024">
* Released [Upstash Workflow](/docs/workflow/getstarted).
</Update>

- [AI Generation](https://upstash.com/docs/workflow/examples/allInOne.md)
- [Auth Provider Webhook](https://upstash.com/docs/workflow/examples/authWebhook.md)
- [Custom Retry Logic](https://upstash.com/docs/workflow/examples/customRetry.md)
- [Customer Onboarding](https://upstash.com/docs/workflow/examples/customerOnboarding.md)
- [Dynamic Workflows](https://upstash.com/docs/workflow/examples/dynamicWorkflow.md)
- [E-commerce Order Fulfillment](https://upstash.com/docs/workflow/examples/eCommerceOrderFulfillment.md)
- [Image Processing](https://upstash.com/docs/workflow/examples/imageProcessing.md)
- [Payment Retries](https://upstash.com/docs/workflow/examples/paymentRetry.md)
- [Waiting for Events](https://upstash.com/docs/workflow/examples/waitForEvent.md)

# Overview
Source: https://upstash.com/docs/workflow/features/dlq

The Dead Letter Queue (DLQ) automatically captures failed workflow runs that have exhausted all retry attempts.

This ensures that no workflow execution is lost and provides multiple options for recovering from failures gracefully.

## How it works?

When a workflow step fails and exhausts all configured retries, Upstash Workflow automatically moves the failed run to the DLQ.
This happens automatically without any additional configuration required.

  <img />

The DLQ serves as a safety net, preserving failed workflow runs with their complete execution context.

<Note>
   Dead Letter Queue entries have retention period based on your pricing plan:
  * **Free**: 3 days
  * **Pay-as-you-go**: 1 week
  * **Fixed pricing**: Up to 3 months

  After the retention duration expires, DLQ items are automatically removed and cannot be recovered.
</Note>

## Recovery Actions

Once a workflow run is in the DLQ, you can take the following actions:

* **[Restart](/docs/workflow/features/dlq/restart)** – trigger the workflow from the beginning.
* **[Resume](/docs/workflow/features/dlq/resume)** – continue the workflow from the point of failure.
* **[Re-run Failure Function](/docs/workflow/features/dlq/callback)** – execute the workflow's failure handling logic again.
* **[Delete](/docs/workflow/features/dlq/delete)** – remove the DLQ entry if no action is required.

You can apply these actions in bulk to multiple DLQ entries. Check the individual action pages for more details.

# Rerun Failure Function
Source: https://upstash.com/docs/workflow/features/dlq/callback

The **Rerun Failure Function** action allows you to retry the failure function that executes when a workflow run enters the Dead Letter Queue (DLQ).

The failure function is typically a cleanup or notification operation that runs automatically whenever a workflow is moved to the DLQ.

This feature is particularly helpful for:

* Ensuring that important cleanup operations are executed.
* Guaranteeing that logging or alerting is completed after a workflow failure.
* Recovering from temporary errors in the failure function itself.

By manually rerunning this function, you can ensure that critical operations—such as cleanup tasks, logging, or alerting—complete successfully even if the main workflow has failed.

  <img />

You can perform this action programmatically as well:

<CodeGroup>
    ```typescript TypeScript
    import { Client } from "@upstash/workflow";

    const client = new Client({ token: "<WORKFLOW_TOKEN>" });

    await client.dlq.retryFailureFunction({
      dlqId: "dlq-12345",
    });
    ```
</CodeGroup>

<Note>
    This action is only available if the failure function itself has failed as well.
    If the failure function already succeeded, it cannot be rerun.

    You can view the status of the failure function in the **DLQ** and **Logs** dashboards, which indicate whether it succeeded or failed.
</Note>

# Delete
Source: https://upstash.com/docs/workflow/features/dlq/delete

The **Delete** action allows you to remove failed workflow runs from the Dead Letter Queue when no recovery action is needed.

You can delete entries individually, in bulk by IDs, or by using filters such as label, URL, or date range.

<CodeGroup>
    ```typescript TypeScript
    import { Client } from "@upstash/workflow";

    const client = new Client({ token: "<WORKFLOW_TOKEN>" });

    // delete a single entry
    await client.dlq.delete("dlq-12345");

    // delete multiple entries
    await client.dlq.delete(["dlq-12345", "dlq-67890"]);

    // delete by filters
    await client.dlq.delete({ label: "my-label" });

    // delete all
    await client.dlq.delete({ all: true });
    ```
</CodeGroup>

For the full API reference, see [client.dlq.delete](/docs/workflow/basics/client/dlq/delete).

# Restart
Source: https://upstash.com/docs/workflow/features/dlq/restart

The **Restart** action allows you to re-execute a failed workflow run from the beginning.
All previous step results are discarded, and the workflow executes from scratch using the original configuration and initial payload.

This approach is ideal when:

* Previous step results are no longer relevant.
* The failure was caused by corrupted or inconsistent state.
* You need a completely fresh execution with updated or clean data.

  <img />

You can perform this action programmatically as well:

<CodeGroup>
    ```typescript TypeScript
    import { Client } from "@upstash/workflow";

    const client = new Client({ token: "<WORKFLOW_TOKEN>" });

    await client.dlq.restart({
      dlqId: "dlq-12345",
      retries: 3,
    });
    ```
</CodeGroup>

# Resume
Source: https://upstash.com/docs/workflow/features/dlq/resume

The **Resume** action allows you to continue a failed workflow run from the exact point of failure, preserving all successfully completed steps and their results.

This approach is ideal when:

* The workflow has long-running or resource-intensive steps that have already succeeded.
* You want to preserve progress and avoid re-executing successful operations.
* The failure was a temporary issue that can now be resolved.

  <img />

You can perform this action programmatically as well:

<CodeGroup>
    ```typescript TypeScript
    import { Client } from "@upstash/workflow";

    const client = new Client({ token: "<WORKFLOW_TOKEN>" });

    await client.dlq.resume({
      dlqId: "dlq-12345",
      retries: 3,
    });
    ```
</CodeGroup>

<Note>
    You can modify workflow code as long as changes occur **after** the failed steps.
    Changes to steps prior to the failure are not allowed and may break the workflow.

    For more details, check out the [Handle workflow route code changes](/docs/workflow/howto/changes) page.
</Note>

# Overview
Source: https://upstash.com/docs/workflow/features/failure-callback

When you define a workflow endpoint, you can attach a failure function to the workflow that allows you to execute custom logic when a workflow run fails after exhausting all retry attempts.

This feature ensures that you can perform cleanup operations, logging, alerting, or any other custom error handling logic before the failed workflow run is moved to the Dead Letter Queue (DLQ).

  <img />

The failure function automatically receives the workflow run context and the reason for the failure, so you can decide how to handle it.

<CodeGroup>
```typescript TypeScript
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve<string>(
  async (context) => {
    // Your workflow logic...
  },
  {
    failureFunction: async ({
      context,
      failStatus,
      failResponse,
      failHeaders,
    }) => {

      // 👇 Log error to monitoring system
      await logToSentry(...);

      // 👇 Send alert to team
      await sendSlackAlert(...);

      // 👇 Perform cleanup operations
      await cleanupWorkflowResources(...);
    },
  }
```
);

</CodeGroup>

You cannot create new workflow steps inside the `failureFunction` using `context`.
The `context` provided here is only meant to expose workflow run properties (like URL, payload, and headers).
Think of the failure function as an individual `context.run` step. It executes once with the provided context but cannot define further steps.

<Info>
    If you use a custom authorization method to secure your workflow endpoint, add authorization to the `failureFunction` too.
    Otherwise, anyone could invoke your failure function with a request.

    Read more here: [securing your workflow endpoint](/docs/workflow/howto/security).
</Info>

## Parameters

The `failureFunction` receives an object with the following parameters:

<ParamField body="context" type="object">
    The workflow context object containing:

    <Expandable>
        <ParamField body="workflowRunId" type="string">
            The ID of the failed workflow run
        </ParamField>

        <ParamField body="url" type="string">
            The publicly accessible workflow endpoint URL
        </ParamField>

        <ParamField body="requestPayload" type="string">
            The original request payload that triggered the workflow
        </ParamField>

        <ParamField body="headers" type="string">
            The original request headers
        </ParamField>

        <ParamField body="env" type="string">
            Environment variables
        </ParamField>
    </Expandable>
</ParamField>

<ParamField body="failStatus" type="number">
    The HTTP status code returned by the failed workflow step.
</ParamField>

<ParamField body="failResponse" type="number">
    The response body returned by the failed workflow step.
</ParamField>

<ParamField body="failHeaders" type="number">
    The response headers returned by the failed workflow step.
</ParamField>

# Advanced failureUrl Option
Source: https://upstash.com/docs/workflow/features/failureFunction/advanced

The `failureUrl` is an advanced option that sends failure callback to a different endpoint rather than to the workflow endpoint (failure function).
This approach is useful for handling failures on separate infrastructure.

<Tip>You can use either `failureFunction` or `failureUrl`, but not both. These options are mutually exclusive.</Tip>

For most users, **Failure Function** is the better choice because:
* It runs alongside your workflow and has access to the same context and dependencies
* Failure function requests are automatically retried on failure as well.
* You can manually retry failure function if it fails via DLQ.

If you think this advanced option fits your need, you can configure it by passing `failureUrl` configuration.

<CodeGroup>
    ```typescript
    import { Client } from "@upstash/workflow";

    const client = new Client({ token: "<QSTASH_TOKEN>" })

    const { workflowRunId } = await client.trigger({
      url: "https://<YOUR_WORKFLOW_URL>/workflow"
      failureUrl: "https://<YOUR_FAILURE_URL>/workflow-failure"
    })
    ```

    ```python Python
    @serve.post("/api/example", failure_url="https://<YOUR_FAILURE_URL>/workflow-failure")
    async def example(context: AsyncWorkflowContext[str]) -> None:
        # Your workflow logic...
        pass
    ```
</CodeGroup>

# Reliability of Failure Function
Source: https://upstash.com/docs/workflow/features/failureFunction/reliability

The failure function is executed whenever a workflow run fails.

In some cases, the failure function itself may throw an error.
When this happens, it will be retried according to the workflow run's retry configuration.
If all retry attempts also fail, the failure function execution is marked as failed.

You can view and filter workflow runs with failed failure function executions in the DLQ dashboard.

  <img />

From the DLQ dashboard, you can retry the failure function.

  <img />

You can perform this action programmatically as well:

```ts
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" });

const response = await client.dlq.retryFailureFunction({
  dlqId: "dlq-12345" // The ID of the DLQ message to retry
});
```

# Overview
Source: https://upstash.com/docs/workflow/features/flow-control

Flow Control allows you to limit how many workflow steps are executed by delaying and queuing their delivery.

This feature helps to:
* Manage resource consumption
* Prevent violations of external API rate limits
* Ensure workflows run within defined system constraints

## How Flow Control Works

When defined limits are exceeded, Flow Control automatically queues and delays step executions instead of rejecting them.
This guarantees that all steps are eventually processed while staying within configured thresholds.

To configure Flow Control, you define a flow control key, a unique identifier used to group related steps under the same rate and parallelism limits.
The steps that has the same flow control key respect the same constraints.

There are two main parameters to configure:

* [Rate and Period](/docs/workflow/features/flow-control/rate-period): Maximum number of steps that may start within a time window
* [Parallelism](/docs/workflow/features/flow-control/parallelism): Maximum number of steps allowed to run concurrently

These parameters can be combined for fine‑grained control.
For example, you can allow up to 10 steps per minute but restrict concurrency
to 5 steps in parallel, ensuring more predictable load patterns.

## Example

Suppose you have the following workflow:

```typescript
export const { POST } = serve<{ topic: string }>(async (context) => {
  const payload = context.requestPayload

  await context.run("step-1", () => { ... });

  await context.run("step-2", () => { ... });

  await context.run("step-3", () => { ... });
})
```

Now imagine you trigger **N workflow runs** for this workflow with the following configuration:

```typescript
const { workflowRunId } = await client.trigger({
  url: "https://<YOUR_WORKFLOW_ENDPOINT>/<YOUR-WORKFLOW-ROUTE>",
  flowControl: {
    key: "fw_example",
    parallelism: 7,
    rate: 3,
    period: "1m",
  }
})
```

Without Flow Control, all workflow runs immediately execute their steps as soon as possible.
If the workflow calls an external API in a step, this would likely result in ~N concurrent requests being fired in a very short timeframe, potentially overloading services or breaching API limits.

  <img />

With the configuration above:
* **Rate:** At most 3 steps per minute can start across all workflow runs.
* **Parallelism:** At most 7 steps can be running at the same time.

Steps that exceed these limits are automatically queued and executed later.

  <img />

Note that each step above corresponds to a separate workflow run.
Because this workflow is sequential, each workflow run has only one pending step at a time.
In workflows with **parallel branches**, multiple steps from the same workflow run may appear in the schedule simultaneously.

Parallelism slots are consumed by running steps.
If no slots are available, new steps enter the **waitlist** until resources free up:

  <img />

<Note>
Upstash Workflow does not support per-step level configuration. Meaning that you can attach a flow-control configuration
to the workflow run and all the steps will inherit to the same limits.
Following the analogy above, you cannot enforce parallelism limit on "green" steps natively.

The context.call and context.invoke steps are exception this to this rule and accept their own flow control configuration:

* [context.call](/docs/workflow/basics/context/call) – lets you run external HTTP requests under a separate key, so you can throttle third‑party API calls independently of your workflow logic.
* [context.invoke](/docs/workflow/basics/context/invoke) – starts a new workflow run with its own flow control configuration. This allows the invoked workflow to run under different limits than the parent workflow, giving you more precise control.

If you want to throttle a specific `context.run` step, the recommended approach is to **extract it into a separate workflow** and call it using `context.invoke()` with its own flow control configuration with a stricter limits.
</Note>

## Configuration

You can configure flow control when starting a workflow run:

```typescript Configure Retry Attempt Count
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" })

const { workflowRunId } = await client.trigger({
  url: "https://<YOUR_WORKFLOW_ENDPOINT>/<YOUR-WORKFLOW-ROUTE>",
  flowControl: {
    key: "user-signup",
    parallelism: 1,
    rate: 10,
    period: 100,
  }
})
```

All steps within a workflow run will adhere to the specified flow control configuration.

<Warning>
Keep in mind that rate/period and parallelism info are kept on each step separately.
If you change the rate/period or parallelism on a new deployment, the old fired ones will not be affected.
They will keep their flow control configuration.

During the period that old steps have not been delivered but there are also steps with new rates, Upstash Workflow will effectively allow the highest rate/period or highest parallelism. Eventually (after the old publishes are delivered), the new rate/period and parallelism will be used.
</Warning>

# Monitor & Manage
Source: https://upstash.com/docs/workflow/features/flow-control/monitor

You can monitor wait list size of your flow control key's from the console `FlowControl` tab. The console also allows you to pin, unpin, and reset rate for flow control keys directly.

  <img />

Also you can get the same info using the REST API.
* [List All Flow Control Keys](/docs/workflow/api-reference/flow-control/list-flow-control-keys).
* [Single Flow Control Key](/docs/workflow/api-reference/flow-control/get-flow-control-key).
* [Global Parallelism](/docs/workflow/api-reference/flow-control/get-global-parallelism).

## Manage

You can manage flow control keys by pausing/resuming delivery, pinning/unpinning configurations, and resetting rate counts.

These operations are available via the SDK, REST API, or directly from the **Flow Control** tab in the [Upstash Console](https://console.upstash.com).

Since Upstash Workflow uses QStash under the hood, flow control management is done through the QStash client. See the [QStash Flow Control Management API](/docs/qstash/features/flowcontrol#management-api) for full details and code examples.

# Parallelism
Source: https://upstash.com/docs/workflow/features/flow-control/parallelism

The parallelism limit controls the maximum number of calls that can be executed concurrently.
Unlike rate limiting (which works per time window), parallelism enforces concurrency control with a token-based system.

```typescript Configure Retry Attempt Count
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" })

const { workflowRunId } = await client.trigger({
  url: "https://<YOUR_WORKFLOW_ENDPOINT>/<YOUR-WORKFLOW-ROUTE>",
  flowControl: {
    key: "user-signup",
    parallelism: 10,
  }
})
```

**Example**:
If `parallelism = 3`, at most 3 requests can run concurrently.

When tokens are available, requests acquire one and start execution:
  <img />

When all tokens are in use, additional requests are not failed — they’re queued in a **waitlist**:
  <img />

The step in the waitlist will wait for a step to complete and hand off it's token to a pending request:

<Tip>
    Token handoff does not guarantee strict ordering.
    A later request in the waitlist may acquire a token before an earlier one.
</Tip>

  <img />

# Rate and Period
Source: https://upstash.com/docs/workflow/features/flow-control/rate-period

The rate specifies the maximum number of requests allowed in a given period (time window).

```typescript Configure Retry Attempt Count
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" })

const { workflowRunId } = await client.trigger({
  url: "https://<YOUR_WORKFLOW_ENDPOINT>/<YOUR-WORKFLOW-ROUTE>",
  flowControl: {
    key: "user-signup",
    rate: 10,
    period: 100,
  }
})
```

**Example**:
If `rate = 2` and `period = 1 minute`, then **a maximum of 2 steps** can be executed per minute.

The first 2 requests within the minute are executed immediately:

  <img />

The 3rd request in the same minute is not executed immediately:

  <img />

Instead of rejecting it, Workflow schedules the request in the next available time window:

  <img />

Note that step executions may take longer than the defined period.
The rate limit only controls how many steps are **started** within each time window,
it does not limit their execution duration.

# Overview
Source: https://upstash.com/docs/workflow/features/invoke

You can start another workflow run inside a workflow and await its execution to complete.
This allows to orchestrate multiple workflows together without external synchronization.

When you use `context.invoke`, invoking workflow will wait until the invoked workflow finishes before running the next step.

```typescript
const {
  body,      // response from the invoked workflow
  isFailed,  // whether the invoked workflow was canceled
  isCanceled // whether the invoked workflow failed
} = await context.invoke(
  "analyze-content",
  {
    workflow: analyzeContent,
    body: "test",
    header: {...}, // headers to pass to anotherWorkflow (optional)
    retries,       // number of retries (optional, default: 3)
    flowControl,   // flow control settings (optional)
    workflowRunId  // workflowRunId to set (optional)
  }
)
```

You can return a response from a workflow, which will be delivered to invoker workflow run.

  <img />

<Note>
    You cannot create an infinite chain of workflow invocations. If you set up an 'invoke loop' where workflows continuously invoke each other, the process will fail once it reaches a depth of 100.
</Note>

# Using Serve Many
Source: https://upstash.com/docs/workflow/features/invoke/serveMany

Normally, workflows are created with `serve()`, which exposes each workflow as its own HTTP endpoint.
If workflows were invoked only by their full URL, it would mean:

* You'd have to provide the URL explicitly like a trigger request
* You'd lose type safety for request and response payloads

To avoid these issues, Upstash Workflow lets you define workflows as objects and expose them under the same parent path.
This way, you can invoke a workflow simply by passing the object to `context.invoke`, with full type safety and no URLs required.

<Steps>
  <Step title="Create workflow objects">
    Use `createWorkflow()` to define workflows as objects.

    It works just like `serve()`—accepting the same arguments—but does **not** expose the workflow directly as an HTTP endpoint.
    Instead, it simply initializes a workflow object.

    ```typescript
    const workflowOne = createWorkflow(
      // 👇 Request Payload Type
      async (context: WorkflowContext<string>) => {

        await context.sleep("wait 1 second", 1)

        // 👇 Workflow Response Type
        return { message: "This is the data returned by the workflow" };
      }
    );

    const workflowTwo = createWorkflow(async (context) => {
      // 👇 Invoke the workflow with type-safe call
      const { body } = await context.invoke(
        "invoke workflowOne",
        {
          workflow: workflowOne,
          body: "user-1"
        }
      ),
    });
    ```
  </Step>
  <Step title="Expose multiple workflows under same endpoint">
    Use `serveMany()` instead of `serve()` to expose multiple workflows on a single catch‑all route.

    If one workflow is going to invoke another, both must be included in the same `serveMany` definition.
    First step of using `serveMany` is to define a catch-all route.

    ```typescript  app/serve-many/[...any]/route.ts
    export const { POST } = serveMany(
      {
        "workflow-one-route": workflowOne,
        "workflow-two-route": workflowTwo,
      }
    )
    ```

    <Info>
        In Next.js, a catch‑all route can be defined by creating a `route.ts` file inside a directory named with `[...]`, for example: `app/serve-many/[...any]/route.ts`.

          For implementations of `serveMany` in other frameworks, you can refer to the projects available in the [`examples` directory of the workflow-js repository](https://github.com/upstash/workflow-js/tree/main/examples).
    </Info>
  </Step>
  <Step title="Invoke by passing workflow object">
    When invoking, pass the workflow object created with `createWorkflow()` (from step 1) as the argument to `context.invoke()`.
    This removes the need to specify a URL explicitly and ensures the call is
    fully type‑safe.

    ```ts
    const workflowTwo = createWorkflow(async (context) => {
      // 👇 Invoke the workflow with type-safe call
      const { body } = await context.invoke(
        "invoke workflowOne",
        {
          // 👇 Pass the workflow object as argument
          workflow: workflowOne,
          body: "user-1",
          // 👇 optionally label the invoked run; single string or array
          label: ["team-a", "high-priority"],
        }
      ),
    });
    ```

  </Step>

  <Step title="Trigger">
    In this example, both `workflowOne` and `workflowTwo` are exposed through `serveMany`, sharing the same parent path.

    You can start `workflowOne` by sending a trigger request to:
    `https://your-app/serve-many/workflow-one-route`.

    ```typescript
    import { Client } from "@upstash/workflow";

    const client = new Client({ token: "<QSTASH_TOKEN>" })

    const { workflowRunId } = await client.trigger({
      // 👇 URL of workflow one
      url: "https://your-app/serve-many/workflow-one-route"
    })
    ```

    Route names are inferred from the keys you pass to `serveMany`.
    For example, you can start `workflowTwo` by sending trigger request to: `https://your-app/serve-many/workflow-two-route`.
    </Step>
</Steps>

# Notify
Source: https://upstash.com/docs/workflow/features/notify

You can notify all the workflow runs waitingi for a specific event ID.
There are two ways to send a notify request.

## Notify within Workflow

Notifies other workflows waiting for a specific event from within a workflow.

<CodeGroup>
```typescript TypeScript
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve<string>(async (context) => {
  const { orderId, processingResult } = context.requestPayload;

  await context.run("process-order", async () => {
    // ...
  })

  const { notifyResponse } = await context.notify(
    "notify-processing-complete",
    `order-${orderId}`,
    {
      orderId,
      status: "completed",
      result: processingResult,
      completedAt: new Date().toISOString()
    }
  );

});
```

```python Python
from fastapi import FastAPI
from upstash_workflow.fastapi import Serve
from upstash_workflow import AsyncWorkflowContext
from datetime import datetime

app = FastAPI()
serve = Serve(app)

@serve.post("/api/order-processor")
async def order_processor(context: AsyncWorkflowContext[str]) -> None:
    order_id = context.request_payload["order_id"]
    processing_result = context.request_payload["processing_result"]

    # Process the order
    async def _process_order():
        return await process_order(order_id)

    result = await context.run("process-order", _process_order)

    # Notify waiting workflows that processing is complete
    notify_response = await context.notify(
        "notify-processing-complete",
        f"order-{order_id}",
        {
            "order_id": order_id,
            "status": "completed",
            "result": processing_result,
            "completed_at": datetime.utcnow().isoformat()
        }
    )

    # Log notification results
    async def _log_notification():
        print(f"Notified {len(notify_response)} waiting workflows")
        return notify_response

    await context.run("log-notification", _log_notification)
```
</CodeGroup>

## External Notification

You can also notify workflows from external systems using the Workflow Client:

<CodeGroup>
```typescript TypeScript
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<WORKFLOW_TOKEN>" });

await client.notify({
  eventId: "order-completed-123",
  eventData: {
    orderId: "123",
    status: "completed",
    deliveryTime: "2 days",
    trackingNumber: "TRK123456"
  }
});
```

```python Python
from upstash_workflow import Client

client = Client("<WORKFLOW_TOKEN>")

# Notify workflows waiting for a specific event
await client.notify(
    event_id="order-completed-123",
    event_data={
        "order_id": "123",
        "status": "completed",
        "delivery_time": "2 days",
        "tracking_number": "TRK123456"
    }
)
```
</CodeGroup>

## Lookback Functionality

By default, if you call `notify` before a workflow reaches its `waitForEvent` step, the notification will be lost (race condition). To prevent this, you can provide a `workflowRunId` parameter which enables **lookback** - the notification will be stored and delivered even if sent before the wait step.

This is particularly useful when:
* You trigger a workflow and immediately want to send it an event
* You have concurrent operations where timing is unpredictable
* You want to eliminate race conditions in your event-driven workflows

<CodeGroup>
```typescript TypeScript
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<WORKFLOW_TOKEN>" });

// Trigger a workflow and get its run ID
const { workflowRunId } = await client.trigger({
  url: "https://your-app.com/api/process-order",
  body: { orderId: "123" }
});

// Immediately notify it with lookback enabled
// The notification will be delivered even if the workflow
// hasn't reached waitForEvent yet
await client.notify({
  eventId: "payment-verified",
  eventData: { verified: true, amount: 100 },
  workflowRunId: workflowRunId, // Enables lookback
});
```

```python Python
from upstash_workflow import Client

client = Client("<WORKFLOW_TOKEN>")

# Trigger a workflow and get its run ID
workflow_run = await client.trigger(
    url="https://your-app.com/api/process-order",
    body={"order_id": "123"}
)

# Immediately notify it with lookback enabled
await client.notify(
    event_id="payment-verified",
    event_data={"verified": True, "amount": 100},
    workflow_run_id=workflow_run.workflow_run_id  # Enables lookback
)
```
</CodeGroup>

The same also applies to `context.notify`

<CodeGroup>
```typescript TypeScript
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve<string>(async (context) => {
  const { orderId, processingResult } = context.requestPayload;

  await context.run("process-order", async () => {
    // ...
  })

  const { notifyResponse } = await context.notify(
    "notify-processing-complete",
    `order-${orderId}`,
    {
      orderId,
      status: "completed",
      result: processingResult,
      completedAt: new Date().toISOString(),
      workflowRunId: "targetWorkflowRunId"  // Enables lookback
    }
  );

});
```
</CodeGroup>

<Note>
When using lookback with `workflowRunId`, the notification is targeted to a specific workflow run rather than all waiters with that event ID.
</Note>

# Parallel Steps
Source: https://upstash.com/docs/workflow/features/parallel-steps

Upstash Workflow supports executing multiple steps in parallel.

Since each step returns a `Promise`, you can execute multiple steps concurrently by using `Promise.all()`.
This behavior works out of the box. No additional configuration is required.

```typescript app/api/workflow/route.ts
import { serve } from "@upstash/workflow/nextjs";
import { checkInventory, brewCoffee, printReceipt } from "@/utils";

export const { POST } = serve(async (context) => {

  // 👇 Execute steps in parallel
  const [coffeeBeansAvailable, cupsAvailable, milkAvailable] =
    await Promise.all([
      context.run("check-coffee-beans", () => checkInventory("coffee-beans")),
      context.run("check-cups", () => checkInventory("cups")),
      context.run("check-milk", () => checkInventory("milk")),
    ]);

});
```

The results of the parallel steps are available as usual once awaited.

The dashboard visualizes parallel execution as shown below:

  <img />

You can also await different step types together. For example, you can run a `context.call()` and a `context.run()` in parallel.

<Warning>
    Whether executing sequentially or in parallel, you should always
    <code>await</code> all promises in a workflow.
    Leaving promises unawaited may cause unexpected behavior.
</Warning>

# Overview
Source: https://upstash.com/docs/workflow/features/retries

Upstash Workflow provides an automatic retry mechanism to improve reliability and make workflows resilient against temporary failures.
Workflow automatically handles transient errors such as network issues or service unavailability.

## How Retries Work

When a step fails, Upstash Workflow automatically retries the failed step with configurable retry attempts and delay strategy.
This allows temporary issues to resolve without manual intervention.

  <img />

By default, the retry count is set to **3**, and an **exponential backoff** delay strategy is used.

```javascript Default Backoff Algorithm
// n = how many times this request has been retried
delay = min(86400, e ** (2.5*n)) // in seconds
```

| Retry Attempt | Algorithm    | Delay  |
|---------------|--------------|--------|
| 1             | $$e^{2.5}$$  | 12s    |
| 2             | $$e^5$$      | 2m28s  |
| 3             | $$e^{7.5}$$  | 30m8s  |
| 4+            | $$86400$$    | 24h    |

## Configuration

You can configure retry behavior when starting a new workflow run.

### Configure Retry Attempt Count

You can specify how many times a step should be retried upon failure.

```typescript Configure Retry Attempt Count
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" })

const { workflowRunId } = await client.trigger({
  url: "https://<YOUR_WORKFLOW_ENDPOINT>/<YOUR-WORKFLOW-ROUTE>",
  retries: 3
})
```

### Configure Retry Delay Strategy

Retry delay is the time to wait before trying again after a failure. You can define a custom retry delay strategy.

The delay is defined as a math expression that is calculated on every retry.
The expression can use the `retried` variable, which represents how many times the step has already retried (starting from 0).

To apply a constant delay, you can simply provide a fixed value.

The expression must return the delay in **milliseconds**.

```typescript Configure Retry Delay Strategy
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" })

const { workflowRunId } = await client.trigger({
  url: "https://<YOUR_WORKFLOW_ENDPOINT>/<YOUR-WORKFLOW-ROUTE>",
  retries: 3,
  retryDelay: "(1 + retried) * 1000"
})
```

# Prevent Retries
Source: https://upstash.com/docs/workflow/features/retries/prevent-retries

It is recommended to enable retries for workflow runs to improve reliability.

However, in some cases, you may want to stop execution immediately when an error occurs, without causing additional retries.
Upstash Workflow provides several mechanisms to terminate workflow execution gracefully.

## Using `WorkflowNonRetryableError`

`WorkflowNonRetryableError` lets you explicitly fail a workflow without entering the retry cycle.

When thrown, the workflow run is marked as failed, which:
* Triggers the failure function (if defined)
* Sends the workflow run to the DLQ

```ts TypeScript highlight={7}
export const { POST } = serve<{ topic: string }>(async (context) => {
  const payload = context.requestPayload

  const isExists = await context.run("is-user-exists", () => { ... });

  if (!isExists) {
    throw new WorkflowNonRetryableError("The user does not exists!")
  }
})
```

## Using `context.cancel()`

You can cancel a workflow run explicitly from inside the workflow.

When canceled, the run is labeled as canceled instead of failed. This means:
* The failure handler will **NOT** be triggered
* The workflow will **NOT** be sent to the DLQ

<CodeGroup>
```typescript highlight={10-11} TypeScript
export const { POST } = serve<{ orderId: string }>(async (context) => {
  const { orderId } = context.requestPayload;

  // Check if order is still valid
  const orderStatus = await context.run("check-order-status", async () => {
    return await getOrderStatus(orderId);
  });

  if (orderStatus === "cancelled") {
    // Stop execution gracefully without error
    await context.cancel();
    return;
  }

  // Continue processing if order is valid
  await context.run("process-order", async () => {
    return await processOrder(orderId);
  });
});
```

```python Python
@serve.post("/graceful-cancellation")
async def graceful_cancellation(context: AsyncWorkflowContext[dict]) -> None:
    order_id = context.request_payload["order_id"]

    async def _check_order_status():
        return await get_order_status(order_id)

    # Check if order is still valid
    order_status = await context.run("check-order-status", _check_order_status)

    if order_status == "cancelled":
        # Stop execution gracefully without error
        await context.cancel()
        return

    # Continue processing if order is valid
    async def _process_order():
        return await process_order(order_id)

    await context.run("process-order", _process_order)
```
</CodeGroup>

## Using conditional execution

You can also use guard conditions to skip certain steps and exit early, without throwing errors or canceling the workflow.

In this case, the workflow run completes successfully because no error was raised.
<CodeGroup>
    ```typescript TypeScript highlight={10-11}
    export const { POST } = serve<{ data: any }>(async (context) => {
      const { data } = context.requestPayload;

      // Check if order is still valid
      const orderStatus = await context.run("check-order-status", async () => {
        return await getOrderStatus(orderId);
      });

      if (orderStatus === "not-found") {
        // Stop execution without error
        return;
      }

      // Continue processing if order is valid
      await context.run("process-order", async () => {
        return await processOrder(orderId);
      });
    });
    ```

    ```python Python
    @serve.post("/conditional-execution")
    async def conditional_execution(context: AsyncWorkflowContext[dict]) -> None:
        data = context.request_payload["data"]

        async def _validate_data():
            return validate_input_data(data)

        # Validate data first
        validation_result = await context.run("validate-data", _validate_data)

        if not validation_result["is_valid"]:
            # Log the validation failure
            async def _log_validation_failure():
                await log_validation_error(validation_result["errors"])

            await context.run("log-validation-failure", _log_validation_failure)

            # Stop execution without error
            return

        # Only execute if validation passes
        async def _process_valid_data():
            return await process_data(data)

        await context.run("process-valid-data", _process_valid_data)
    ```
</CodeGroup>

# Sleep
Source: https://upstash.com/docs/workflow/features/sleep

Upstash Workflow provides a **Sleep** feature that allows you to pause workflow execution for specified durations without consuming compute resources.

This feature enables you to build time-based workflows, implement delays between steps, and create scheduled operations without the limitations of traditional serverless timeouts.

## How Sleep Works

When you use `context.sleep` or `context.sleepUntil` in your workflow, Upstash Workflow automatically pauses execution and schedules the next step to run after the specified delay.
This happens without keeping your serverless function running, making it cost-effective and reliable for long delays.

<Note>
  **Important:** Sleep durations have limits based on your pricing plan:
  * **Free**: Maximum delay of 7 days
  * **Pay-as-you-go**: Maximum delay of 1 year
  * **Fixed pricing**: Custom delays (no limit)
</Note>

## Sleep Methods

Upstash Workflow provides two methods for implementing delays in your workflows:

### 1. context.sleep

Pauses workflow execution for a specified duration relative to the current time.

<CodeGroup>
```typescript TypeScript
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve<string>(async (context) => {
  const { userId } = context.requestPayload;

  // Send welcome email immediately
  await context.run("send-welcome-email", async () => {
    return await sendWelcomeEmail(userId);
  });

  // Wait for 3 days before sending follow-up
  await context.sleep("wait-for-follow-up", "3d");

  // Send follow-up email
  await context.run("send-follow-up-email", async () => {
    return await sendFollowUpEmail(userId);
  });
});
```

```python Python
from fastapi import FastAPI
from upstash_workflow.fastapi import Serve
from upstash_workflow import AsyncWorkflowContext

app = FastAPI()
serve = Serve(app)

@serve.post("/api/onboarding")
async def onboarding(context: AsyncWorkflowContext[str]) -> None:
    user_id = context.request_payload["user_id"]

    # Send welcome email immediately
    async def _send_welcome_email():
        return await send_welcome_email(user_id)

    await context.run("send-welcome-email", _send_welcome_email)

    # Wait for 3 days before sending follow-up
    await context.sleep("wait-for-follow-up", "3d")

    # Send follow-up email
    async def _send_follow_up_email():
        return await send_follow_up_email(user_id)

    await context.run("send-follow-up-email", _send_follow_up_email)
```
</CodeGroup>

You can specify durations using human-readable strings:

* `"10s"` = 10 seconds
* `"1m"` = 1 minute
* `"30m"` = 30 minutes
* `"2h"` = 2 hours
* `"1d"` = 1 day
* `"1w"` = 1 week
* `"1mo"` = 1 month
* `"1y"` = 1 year

You can also use numeric values in seconds:

* `60` = 60 seconds (1 minute)
* `3600` = 3600 seconds (1 hour)
* `86400` = 86400 seconds (1 day)

### 2. context.sleepUntil

Pauses workflow execution until a specific timestamp in the future.

<CodeGroup>
```typescript TypeScript
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve<string>(async (context) => {
  const { userId, scheduledTime } = context.requestPayload;

  // Calculate the scheduled time
  const scheduledDate = new Date(scheduledTime);

  // Wait until the scheduled time
  await context.sleepUntil("wait-until-scheduled", scheduledDate);

  // Execute the scheduled task
  await context.run("execute-scheduled-task", async () => {
    return await executeTask(userId);
  });
});
```

```python Python
from fastapi import FastAPI
from upstash_workflow.fastapi import Serve
from upstash_workflow import AsyncWorkflowContext
from datetime import datetime

app = FastAPI()
serve = Serve(app)

@serve.post("/api/scheduled-task")
async def scheduled_task(context: AsyncWorkflowContext[str]) -> None:
    user_id = context.request_payload["user_id"]
    scheduled_time = context.request_payload["scheduled_time"]

    # Calculate the scheduled time
    scheduled_date = datetime.fromisoformat(scheduled_time)

    # Wait until the scheduled time
    await context.sleep_until("wait-until-scheduled", scheduled_date)

    # Execute the scheduled task
    async def _execute_task():
        return await execute_task(user_id)

    await context.run("execute-scheduled-task", _execute_task)
```
</CodeGroup>

For `context.sleepUntil`, you can use:

* `Date` objects (JavaScript/TypeScript)
* Unix timestamps (Python)
* ISO string dates

<Tip>
  Sleep operations have a precision of approximately 1 second. Very short delays (less than 1 second) may not be exact.
</Tip>

The sleep feature in Upstash Workflow provides a powerful way to create time-based, reliable workflows without the limitations of traditional serverless timeouts.

 By leveraging this feature, you can build sophisticated business logic that spans hours, days, or even months while maintaining cost efficiency and reliability.

# Wait
Source: https://upstash.com/docs/workflow/features/wait

You can pause a workflow run with the `waitForEvent` step. An event is uniquely identified by event ID.

The workflow will resume when the matching event is published.

`waitForEvent` supports configurable timeouts to prevent workflows from waiting indefinitely.
When a timeout occurs, the returned object includes `timeout: true`, allowing you to handle the failure case gracefully (for example, cancel an order, notify the user, or retry later).

<Tip>
If no timeout is specified, the default is **7 days**.
</Tip>

<CodeGroup>
```typescript TypeScript
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve<string>(async (context) => {
  const { orderId, userEmail } = context.requestPayload;

  // Wait for order processing completion
  const { eventData, timeout } = await context.waitForEvent(
    "wait-for-order-processing",
    `order-${orderId}`,
    {
      timeout: "1d" // 1 day timeout
    }
  );

  if (timeout) {
    // Handle timeout scenario
    await context.run("handle-timeout", async () => {
      return await handleOrderTimeout(orderId, userEmail);
    });
    return;
  }

});
```

```python Python
from fastapi import FastAPI
from upstash_workflow.fastapi import Serve
from upstash_workflow import AsyncWorkflowContext

app = FastAPI()
serve = Serve(app)

@serve.post("/api/order-processing")
async def order_processing(context: AsyncWorkflowContext[str]) -> None:
    order_id = context.request_payload["order_id"]
    user_email = context.request_payload["user_email"]

    # Send order processing request
    async def _request_order_processing():
        return await request_order_processing(order_id)

    await context.run("request-order-processing", _request_order_processing)

    # Wait for order processing completion
    result = await context.wait_for_event(
        "wait-for-order-processing",
        f"order-{order_id}",
        timeout="10m"  # 10 minutes timeout
    )

    if result["timeout"]:
        # Handle timeout scenario
        async def _handle_timeout():
            return await handle_order_timeout(order_id, user_email)

        await context.run("handle-timeout", _handle_timeout)
        return

    # Process the completed order
    async def _process_completed_order():
        return await process_completed_order(order_id, result["event_data"])

    await context.run("process-completed-order", _process_completed_order)
```
</CodeGroup>

# Overview
Source: https://upstash.com/docs/workflow/features/wait-for-event

Wait for Event feature that allows you to pause workflow execution until an external event occurs.

This feature enables you to build asynchronous workflows that can wait for user interactions, external system responses, or any other events without consuming compute resources.

## How Wait for Event Works

When you use `context.waitForEvent()` in your workflow, Upstash Workflow automatically pauses execution and waits for an external notification to resume.
This happens without keeping your serverless function running, making it cost-effective and reliable for event-driven workflows.

Each waiter has a timeout duration to wait for the event and then fires automatically.

<Note>
   Wait for Event timeouts have limits based on your pricing plan:
  * **Free**: Maximum timeout of 7 days
  * **Pay-as-you-go**: Maximum timeout of 1 year
  * **Fixed pricing**: Custom timeouts (no limit)
</Note>

## Race Condition Between Wait and Notify

A race condition can occur when `notify` is called before `waitForEvent` is executed.
In this scenario, the notification will be sent but no workflow will be waiting to receive it, causing the event to be lost.

### Solutions

There are three ways to handle race conditions:

1. **Use lookback with `workflowRunId`** (Recommended for targeted notifications)
2. **Use [Webhooks](/docs/workflow/features/webhooks)** (Recommended for general use)
3. **Check and retry** (Manual approach)

#### Option 1: Lookback with workflowRunId

When you know which specific workflow run should receive the notification, you can provide a `workflowRunId` to enable lookback. The notification will be stored and delivered even if sent before `waitForEvent`:

<CodeGroup>
```typescript TypeScript
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<WORKFLOW_TOKEN>" });

// Trigger a workflow
const { workflowRunId } = await client.trigger({
  url: "https://your-app.com/api/process-order",
  body: { orderId: "123" }
});

// Immediately notify with lookback - no race condition!
await client.notify({
  eventId: "payment-verified",
  eventData: { verified: true },
  workflowRunId: workflowRunId, // Enables lookback
});
```
</CodeGroup>

#### Option 2: Use Webhooks

[Webhooks](/docs/workflow/features/webhooks) have built-in lookback and are safer against timing issues for general event handling.

#### Option 3: Check and Retry

Alternatively, you can check the response of the `notify` operation and retry if needed:

<CodeGroup>
```typescript TypeScript
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<WORKFLOW_TOKEN>" });

const result = await client.notify({
    eventId,
    eventData
});

// Check if any workflows were notified
if (result.waiters && result.waiters.length > 0) {
    console.log(`Notified ${result.waiters.length} workflows`);
    return result;
}

// If no workflows were waiting, wait and retry once
console.log("No workflows waiting, retrying in 5 seconds...");
await new Promise(resolve => setTimeout(resolve, 5000));

return await client.notify({
    eventId,
    eventData
});
```
</CodeGroup>

## Selecting an Event ID

When a workflow run waits on an event ID, it's appended to a list of waiters for the event ID.

When a notify request is sent, all workflow runs waiting for that event are notified sequentially.
To avoid heavy notify operations, it’s recommended to use unique event IDs instead of generic ones.
For example, instead of waiting on `user-sent-verification`, wait the workflow on `user-{userId}-sent-verification` event.

# Webhooks
Source: https://upstash.com/docs/workflow/features/webhooks

The Webhook feature allows you to pause workflow execution and wait for a webhook URL to be called, enabling seamless integration with third-party APIs and asynchronous operations.

This feature is perfect for scenarios where you need to:
* Wait for external API processing to complete
* Integrate with services that use callback URLs
* Receive notifications from external webhooks

## How Webhooks Work

When you use webhooks in your workflow, Upstash Workflow:

1. **Creates a unique webhook URL** via `context.createWebhook()`
2. **Provides the URL to external services** (typically through `context.call()`)
3. **Pauses execution** via `context.waitForWebhook()` until the webhook is called
4. **Resumes workflow** when the webhook receives a request or timeout is reached

This happens without keeping your serverless function running, making it cost-effective for long-running integrations.

## Examples

### Basic Usage

```typescript
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve(async (context) => {
  // Step 1: Create webhook
  const webhook = await context.createWebhook("create webhook");

  // Step 2: Call an external endpoint, which calls the webhookUrl upon completion
  const callResult = await context.call("call webhook caller", {
    url: "https://webhook/caller",
    method: "POST",
    body: JSON.stringify({
      webhookUrl: webhook.webhookUrl,
    }),
  });

  // Step 3: Wait for the webhook to be called
  const webhookResponse = await context.waitForWebhook(
    "wait for webhook",
    webhook,
    "30s" // timeout
  );

  if (webhookResponse.timeout) {
    console.log("Webhook was not called in time");
    // Handle timeout scenario
  } else {
    console.log("Webhook received:", webhookResponse.request);
    // Process the webhook data
  }
});
```

### Waiting for Multiple Calls

Some services send multiple progress updates to a webhook URL as processing continues. You can wait for the same webhook multiple times in a loop until you receive a final "finished" signal:

```typescript
while (true) {
  const webhookResponse = await context.waitForWebhook(
    `wait for progress update ${stepCount}`,
    webhook,
    "5m" // 5 minute timeout between updates
  );

  if (webhookResponse.timeout) {
    console.log("No progress update received in time, exiting");
    break;
  } else {
    const request = webhookResponse.request;
    console.log("Progress update received:", await request.json());

    if (request.headers.get("x-task-finished") === "true") {
      console.log("Task finished, exiting loop");
      break;
    }
  }
}
```

## Race Condition Safety

Webhooks have built-in **lookback** protection, making them safer against race conditions. If an external service calls the webhook URL before `context.waitForWebhook()` is executed, the webhook call is stored and will be returned when `waitForWebhook` is called.

This means you don't need to worry about timing issues between creating the webhook and waiting for it - the workflow will always receive the callback even if it arrives early.

## Comparison with Wait for Event

Webhooks and [Wait for Event](/docs/workflow/features/wait-for-event) serve similar purposes but with different approaches:

| Feature | Webhooks | Wait for Event |
|---------|----------|----------------|
| **Trigger** | External HTTP call to unique URL | Notify via Upstash Workflow Client |
| **Setup** | Create webhook, pass URL to external service | Share event ID with external service |
| **Integration** | Easy with callback-based APIs | Requires Workflow Client integration |
| **Lookback** | ✅ Yes - safe against race conditions | ❌ No - notify before wait will be lost |
| **Best For** | Third-party API callbacks | Internal event notifications |

Choose webhooks when integrating with external services that support callback URLs, or when you need protection against race conditions. Choose Wait for Event when you have control over the notification mechanism and can use the Workflow Client.

## API Reference

<CardGroup cols={2}>
  <Card title="context.createWebhook" icon="webhook" href="/workflow/basics/context/createWebhook">
    Create a unique webhook URL for external services to call
  </Card>
  <Card title="context.waitForWebhook" icon="clock" href="/workflow/basics/context/waitForWebhook">
    Wait for the webhook to be called or timeout
  </Card>
</CardGroup>

# Overview
Source: https://upstash.com/docs/workflow/getstarted

import { Catalog } from "/snippets/catalog.jsx";

Upstash Workflow lets you write **durable, reliable and performant serverless functions**. Get delivery guarantees, automatic retries on failure, scheduling and more without managing any infrastructure.

<iframe
  id="intro-video"
  width="720"
  height="405"
  src="https://www.youtube.com/embed/5As9tZMN3a8?rel=0&disablekb=1"
  title="YouTube video player"
  frameBorder="0"
  allow="accelerometer; fullscreen; clipboard-write; encrypted-media; gyroscope"
  allowFullScreen
></iframe>

## Quickstarts

Upstash Workflow supports Next.js, Cloudflare Workers and [many other frameworks](/docs/workflow/quickstarts/platforms) in TypeScript and Python.

<CardGroup cols={2}>
  <Card
    title="Next.js"
    icon="node-js"
    href="/workflow/quickstarts/vercel-nextjs"
  >
    Build a Next.js application with QStash Workflow
  </Card>
  <Card
    title="Cloudflare Workers"
    icon="cloudflare"
    href="/workflow/quickstarts/cloudflare-workers"
  >
    Use and deploy Upstash Workflow on Cloudflare Workers
  </Card>
  <Card
    title="Next.js & FastAPI"
    icon="python"
    href="/workflow/quickstarts/nextjs-fastapi"
  >
    Use Upstash Workflow for Python with Next.js and FastAPI
  </Card>
</CardGroup>

## Key Features

<CardGroup cols={2}>
  <Card
    title="Failure Resilience"
    icon="shield"
  >
    If your platform experiences a temporary outage, your workflow can pick up right where it left off, ensuring stability even in unstable environments.
  </Card>
  <Card
    title="Long-Running Executions"
    icon="clock"
  >
    Run long-running REST endpoints, such as complex AI models or video processing tools, even on serverless platforms with strict time limits.
  </Card>
  <Card
    title="Events with Wait/Notify Mechanism"
    icon="bell"
    href="/workflow/features/wait-for-event"
  >
    Create workflows that wait for external events before proceeding. Ideal for user confirmations and asynchronous notifications.
  </Card>
  <Card
    title="Scheduled Jobs"
    icon="calendar"
    href="/workflow/howto/schedule"
  >
    Run jobs at regular intervals with support for cron expressions. Perfect for recurring tasks like reminders, reports, or newsletters.
  </Card>
  <Card
    title="Parallel Runs"
    icon="road"
    href="/workflow/features/parallel-steps"
  >
    Start independent tasks in parallel and wait for them to finish simultaneously, reducing latency.
  </Card>
  <Card
    title="Long Delays"
    icon="hourglass"
    href="/workflow/basics/context/sleep"
  >
    Need your code to “sleep” for days, weeks, or even months? Supports long delays beyond serverless time limits.
  </Card>
  <Card
    title="Delivery Guarantees"
    icon="rotate-right"
  >
    Ensures at-least-once delivery. Failed requests are logged in a Dead Letter Queue to prevent data loss.
  </Card>
  <Card
    title="Flow Control"
    icon="gauge"
    href="/workflow/features/flow-control"
  >
    Prevent overwhelming your app or external services by configuring rate per second or parallelism limits.
  </Card>
  <Card
    title="Observability"
    icon="eye"
    href="/workflow/basics/client/logs"
  >
    Monitor workflow steps with insights. Filter events to track successes, failures, retries, and stalls.
  </Card>
</CardGroup>

## Example Use Cases

Here are some example real world use-cases for Upstash Workflow:

<CardGroup cols={2}>
  <Card title="Agents" icon="robot" href="/workflow/agents/overview">
    Use LLM Agents equipped with custom tools to achieve tasks
  </Card>
  <Card title="AI Data Processing" icon="sparkles" href="/workflow/examples/allInOne">
    Download a large dataset without timeouts, process the data in chunks and generate a report.
  </Card>
  <Card
    title="Waiting For Events"
    icon="traffic-light"
    href="/workflow/examples/waitForEvent"
  >
    Control workflow execution with events, log event data and send emails
  </Card>
  <Card
    title="Authorization Webhook"
    icon="webhook"
    href="/workflow/examples/authWebhook"
  >
    Start a workflow from a webhook. Handle user creation, trial management,
    email reminders and notifications.
  </Card>
  <Card
    title="Customer Onboarding"
    icon="user"
    href="/workflow/examples/customerOnboarding"
  >
    Register a new user, send welcome emails, and periodically check and respond
    to the user's activity state with emails.
  </Card>
  <Card
    title="E-Commerce Order Fulfillment"
    icon="cart-flatbed-boxes"
    href="/workflow/examples/eCommerceOrderFulfillment"
  >
    Receive an order request, verify the stock, process the payment, and handle
    order dispatch and customer notifications.
  </Card>
  <Card
    title="Image Processing"
    icon="image"
    href="/workflow/examples/imageProcessing"
  >
    Manage uploading images to the data store. Apply filters and resize the
    images to different resolutions in parallel.
  </Card>
  <Card
    title="Retry Payments"
    icon="credit-card"
    href="/workflow/examples/paymentRetry"
  >
    Retry payments with a day of delay, send emails, and suspend account if
    payment fails after the retries.
  </Card>
</CardGroup>

## How it works

Upstash Workflow builds on the principle of steps. Instead of defining a single, complex piece of business logic, workflows contain multiple individual steps.

Each of the steps are executed by a separate request to your application, by preserving the output of previous steps.

In case of an error, a failed step is retried individually without needing to re-run any previous steps. Instead of the entire business logic, _each step_ can take up your serverless function execution duration, and many more benefits.

  <img />

## Support

Need help or have questions? We're here to support you:

* Join our Discord community to ask questions and share feedback
* Open a ticket through the Intercom chatbox in the dashboard for any issue

## SDKs

<Catalog product="workflow" type="sdk" cols={3} />

# Cancel a Run
Source: https://upstash.com/docs/workflow/howto/cancel

You can cancel a running workflow both programatically and from your Upstash Workflow console.

## Cancelling via console

In your Upstash Workflow console, find the run you'd like to cancel and press the `Cancel Workflow` button on the right side:

  <img />

## Cancelling programatically

<Note>
  This feature is not yet available in
  [workflow-py](https://github.com/upstash/workflow-py). See our
  [Roadmap](/docs/workflow/roadmap) for feature parity plans and
  [Changelog](/docs/workflow/changelog) for updates.
</Note>

```javascript
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" });
await client.cancel({ ids: "<WORKFLOW_RUN_ID>" });
```

And replace `<WORKFLOW_RUN_ID>` with your actual run ID. See [the documentation of `client.cancel` method for more information about other ways of canceling workflows](/docs/workflow/basics/client/cancel).

You can also use the [Upstash Workflow REST API](/docs/workflow/api-reference/runs/cancel-workflow-run) to cancel a run programatically.

# Update a Workflow
Source: https://upstash.com/docs/workflow/howto/changes

Workflows are composed of multiple steps. When you modify workflow code, it's important to consider how these changes might affect in-progress workflows.

## Issues

You cannot change the step order of an existing workflow.

If your code changes remove or reorder existing steps, in-progress workflows may attempt to continue from a point that no longer exists. This can lead to workflow failures, typically resulting in the following error:

```bash
HTTP status 400. Incompatible step name. Expected <STEP_NAME>, got <STEP_NAME>
```

## Safe changes

Updating workflow code is safe in the following cases:

* No active workflow runs exist
* Only new steps are added to the end of the workflow

## Guidelines for updating workflows

Consider the following approaches when updating your workflow code:

* **Accept potential failures:** If you're fine with in-progress workflows failing, you can make any code changes.
* **Use a different route:** To avoid failures, consider serving the updated workflow under a different route.
* **Stop traffic before deployment:** If you need to keep the same route, stop all traffic before deploying new code.
* **Add steps only:** If stopping traffic is not an option, limit your changes to adding new steps at the end of the workflow.

For a deeper understanding of these limitations, see our [how workflows work](/docs/workflow/basics/how) section.

# Configure a Run
Source: https://upstash.com/docs/workflow/howto/configure

You can configure a workflow run when starting it. The following are the options you can configure:

1. Retries: The number of retry attempt Upstash Workflow does when a step fails in the workflow run
2. Retry Delay: The delay strategy between retries when Upstash Workflow attempts retries.
3. Flow Control: The rate, period and parallelism that steps should respect and logical grouping key to share with other workflow runs.

You can pass these configuration options when starting a workflow run:

```typescript
import { Client } from "@upstash/workflow";

const client = Client()

const { workflowRunId } = await client.trigger({
    url: `http://localhost:3000/api/workflow`,
    retries: 3,
    retryDelay: "(1 + retries) * 1000",
    flowControl: {
        key: "limit-ads",
        rate: 1,
        parallelism: 10
    }
});
```

The workflow run configuration does **not** apply to `context.call()` and `context.invoke()` steps.
These steps accept their own configuration options, allowing fine-grained control over external requests.
If not specified, they fall back to their default values.

For details, see:
* [context.call](/docs/workflow/basics/context/run)
* [context.invoke](/docs/workflow/basics/context/run)

<Tip>
Upstash Workflow does not support step level configuration. The configuration applies to all steps executed by a workflow run.

If you want to specifically throttle a step, there is a workaround by splitting step to another workflow and using `context.invoke()`.
</Tip>

# Handle Failed Runs
Source: https://upstash.com/docs/workflow/howto/failures

This guide shows you how to **gracefully handle failed workflow runs**. This involves best practices on resolving runtime errors, logging and manually retrying runs that have failed multiple times.

## Why a workflow might fail

* A step in your workflow throws a database error that causes your code to fail at runtime.
* QStash calls your workflow URL, but the URL is not reachable - for example, because of a temporary outage of your deployment platform.
* A single step takes longer than your platform's function execution limit.

Workflow automatically retries a failed step based on your configuration (by default, it retries three times with exponential backoff).
This helps handle temporary outages or intermittent failures gracefully.

  <img />

If, even after all retries, your step does not succeed, we'll move the failed run into your [Dead Letter Queue (DLQ)](/docs/qstash/howto/handling-failures#dead-letter-queue). That way, you can always manually retry it again and debug the issue.

  <img />

If you want to take an action (a cleanup/log), you can configure either `failureFunction` or a `failureUrl` on the `serve` method of your workflow.
These options allow you to define custom logic or an external endpoint that will be triggered when a failure occurs.

## Using a `failureFunction` (recommended)

The `serve` function you use to create a workflow endpoint accepts a `failureFunction` parameter - an easy way to gracefully handle errors (i.e. logging them to Sentry) or your custom handling logic.

<CodeGroup>
```typescript TypeScript
export const { POST } = serve<string>(
  async (context) => {
    // Your workflow logic...
  },
  {
    failureFunction: async ({
      context,
      failStatus,
      failResponse,
      failHeaders,
    }) => {
      // Handle error, i.e. log to Sentry
      console.error("Workflow failed:", failResponse);

      // You can optionally return a string that will be visible
      // in the UI (coming soon) and in workflow logs
      return `Workflow failed with status ${failStatus}: ${failResponse}`;
    },
  }
);
```

```python Python
async def failure_function(
    context,       # context during failure
    fail_status,   # failure status
    fail_response, # failure message
    fail_headers   # failure headers
):
    # handle the failure
    pass

@serve.post("/api/example", failure_function=failure_function)
async def example(context: AsyncWorkflowContext[str]) -> None: ...
```

</CodeGroup>

Note: If you use a custom authorization method to secure your workflow endpoint, add authorization to the `failureFunction` too. Otherwise, anyone can invoke your failure function. Read more here: [securing your workflow endpoint](/docs/workflow/howto/security).

In `@upstash/workflow`, the `failureFunction` can optionally return a string value that will be displayed in the UI (coming soon) and included in the workflow logs. This is useful for providing custom error messages, debugging information, or tracking specific failure conditions.

## Using a `failureUrl`

Instead of using the built-in failure function, you can define a separate failure callback URL.
Unlike the failure function, which only works when your application is running, the failure URL allows you to handle errors even if your application is completely down.
If the URL is a different service other than your application, it will be reachable in these cases.

By pointing the failure URL to an external service (not hosted within your main application), you ensure that it remains accessible even when your primary app is unavailable.

<CodeGroup>

```typescript TypeScript
export const { POST } = serve<string>(
  async (context) => {
    // Your workflow logic...
  },
  {
    failureUrl: "https://<YOUR_FAILURE_URL>/workflow-failure",
  }
);
```

```python Python
@serve.post("/api/example", failureUrl="https://<YOUR-FAILURE-ENDPOINT>/...")
async def example(context: AsyncWorkflowContext[str]) -> None: ...
```

</CodeGroup>

The callback body sent to you will be a JSON object with the following fields:

```javascript JavaScript
{
  "status": 200,
  "header": { "key": ["value"] },         // Response header
  "body": "YmFzZTY0IGVuY29kZWQgcm9keQ==", // base64 encoded response body
  "retried": 2,                           // How many times we retried to deliver the original message
  "maxRetries": 3,                        // Number of retries before the message assumed to be failed to delivered.
  "sourceMessageId": "msg_xxx",           // The ID of the message that triggered the callback
  "topicName": "myTopic",                 // The name of the URL Group (topic) if the request was part of a URL Group
  "endpointName": "myEndpoint",           // The endpoint name if the endpoint is given a name within a topic
  "url": "http://myurl.com",              // The destination url of the message that triggered the callback
  "method": "GET",                        // The http method of the message that triggered the callback
  "sourceHeader": { "key": "value" },     // The http header of the message that triggered the callback
  "sourceBody": "YmFzZTY0kZWQgcm9keQ==",  // The base64 encoded body of the message that triggered the callback
  "notBefore": "1701198458025",           // The unix timestamp of the message that triggered the callback is/will be delivered in milliseconds
  "createdAt": "1701198447054",           // The unix timestamp of the message that triggered the callback is created in milliseconds
  "scheduleId": "scd_xxx",                // The scheduleId of the message if the message is triggered by a schedule
  "callerIP": "178.247.74.179"            // The IP address where the message that triggered the callback is published from
}
```

In Next.js you can use the following code to handle the callback:

```javascript JavaScript
// pages/api/callback.js

import { verifySignature } from "@upstash/qstash/nextjs";

function handler(req, res) {
  // responses from qstash are base64-encoded
  const decoded = atob(req.body.body);
  console.log(decoded);

  return res.status(200).end();
}

export default verifySignature(handler);

export const config = {
  api: {
    bodyParser: false,
  },
};
```

`verifySignature` allows to verify the signature of request, which is signed by Upstash using your signing keys.
If you don't want to verify the signature, you can remove `QSTASH_CURRENT_SIGNING_KEY` and `QSTASH_NEXT_SIGNING_KEY` environment variables and remove `verifySignature` function.

## Manually Handling Failed Workflow Runs

When a workflow run fails and is moved to the Dead Letter Queue (DLQ), you have several options to handle it manually via the REST API:

### [Resume](/docs/workflow/api-reference/dlq/resume-workflow-from-dlq)
* **What it does:** Continues a failed workflow run from exactly where it failed, preserving all successful step results.
* **When to use:** Use this if you want to retry only the failed/pending steps without re-executing the entire workflow.

### [Restart](/docs/workflow/api-reference/dlq/restart-workflow-from-dlq)
* **What it does:** Starts the failed workflow run over from the beginning, discarding all previous step results.
* **When to use:** Use this if you want a clean execution, or if the failure may have been caused by a corrupted state that requires a fresh start.

### [Callback](/docs/workflow/api-reference/dlq/retry-failure-callback)
* **What it does:** Reruns the failure callback for a workflow run, in case the original failure callback was not delivered or failed.
* **When to use:** Use this to ensure your system is notified of workflow failures, even if the original callback attempt did not succeed.

## Debugging failed runs

In your DLQ, filter messages via the `Workflow URL` or `Workflow Run ID` to search for a particular failure. We include all request and response headers and bodies to simplify debugging failed runs.

For example, let's debug the following failed run. Judging by the status code `404`, the `Ngrok-Error-Code` header of `ERR_NGROK_3200` and the returned HTML body, we know that the URL our workflow called does not exist.

  <img />

# Flow control
Source: https://upstash.com/docs/workflow/howto/flow-control

# Get your QStash Token
Source: https://upstash.com/docs/workflow/howto/get-token

Upstash Workflow is powered by [QStash](/docs/qstash/overall/getstarted), so your app
authenticates with a QStash token. Go to the
[Workflow tab in the Upstash Console](https://console.upstash.com/workflow), pick
the region you want to use, and copy the `QSTASH_TOKEN` from the **Quickstart**
section.

  <img />

<Info>
If you want to safely share a token with your team or AI agents, you can use a
[read-only token](/docs/workflow/howto/readonly-token) instead.
</Info>

# Development Server
Source: https://upstash.com/docs/workflow/howto/local-development/development-server

Upstash Workflow is built on top of Upstash QStash.
The QStash CLI provides a local development server that performs QStash functionality locally for development and testing purposes.

## Automatic dev server (recommended)

If you are using `@upstash/workflow`, you can just set `QSTASH_DEV=true` in your environment, and the SDK will download and connect to the dev server automatically. No tokens or signing keys required.

```bash .env
QSTASH_DEV=true
```

With `QSTASH_DEV=true` set, both the workflow client and the `serve()` endpoint pick up the dev server automatically. The endpoint also verifies incoming signatures against the dev server's deterministic signing keys, so signature verification works end-to-end with no extra setup.

```typescript
// app/api/workflow/route.ts
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve(async (context) => {
  await context.run("step-1", () => console.log("running locally"));
});
```

```typescript
import { Client } from "@upstash/workflow";

const client = new Client({ token: process.env.QSTASH_TOKEN ?? "" });

await client.trigger({
  url: "http://localhost:3000/api/workflow",
});
```

For details on the dev server behavior, ports, and the `registerQStashDev()` helper for Next.js edge routes, see the [QStash Local Development docs](/docs/qstash/howto/local-development).

## Manual setup

If you would rather start and manage the QStash dev server yourself, follow the steps below.

<Steps>
    <Step title="Install and Start Development Server">
        Start the development server using the QStash CLI:

        ```javascript
        npx @upstash/qstash-cli dev
        ```

        The QStash CLI output will look something like this:

        ```plaintext QStash CLI Output
        Upstash QStash development server is runnning at

        A default user has been created for you to authorize your requests.
        QSTASH_TOKEN=eyJVc2VySUQiOiJkZWZhdWx0VXNlciIsIlBhc3N3b3JkIjoiZGVmYXVsdFBhc3N3b3JkIn0=
        QSTASH_CURRENT_SIGNING_KEY=sig_7RvLjqfZBvP5KEUimQCE1pvpLuou
        QSTASH_NEXT_SIGNING_KEY=sig_7W3ZNbfKWk5NWwEs3U4ixuQ7fxwE

        Sample cURL request:
        curl -X POST http://127.0.0.1:8080/v2/publish/https://example.com -H "Authorization: Bearer eyJVc2VySUQiOiJkZWZhdWx0VXNlciIsIlBhc3N3b3JkIjoiZGVmYXVsdFBhc3N3b3JkIn0="

        Check out documentation for more details:
        https://upstash.com/docs/qstash/howto/local-development
        ```

        For detailed instructions on setting up the development server, see our [QStash Local Development Guide](/docs/qstash/howto/local-development).
    </Step>

    <Step title="Enable Local Mode on Console">
        Once you start the local server, you can go to the Workflow tab on Upstash Console and enable local mode, which will allow you to monitor and debug workflow runs with the local server.

        <img />
    </Step>

    <Step title="Update Environment Variables">
        Once your development server is running, update your environment variables to route QStash requests to your local server.

        ```env
        QSTASH_URL="http://127.0.0.1:8080"
        QSTASH_TOKEN="eyJVc2VySUQiOiJkZWZhdWx0VXNlciIsIlBhc3N3b3JkIjoiZGVmYXVsdFBhc3N3b3JkIn0="
        QSTASH_CURRENT_SIGNING_KEY="sig_7RvLjqfZBvP5KEUimQCE1pvpLuou"
        QSTASH_NEXT_SIGNING_KEY="sig_7W3ZNbfKWk5NWwEs3U4ixuQ7fxwE"
        ```

    </Step>

    <Step title="Use local addresses">
        It's all set up 🎉

        Now, you can use your local address when triggering the workflow runs.

        ```javascript
        import { Client } from "@upstash/workflow";

        const client = Client()

        const { workflowRunId } = await client.trigger({
            url: `http://localhost:3000/api/workflow`,
            retries: 3
        });
        ```
        <Tip>
        Inside the `trigger()` call, you need to provide the URL of your workflow endpoint:

        * Local development → use the URL where your app is running, for example: http://localhost:3000/api/PATH
        * Production → use the URL of your deployed app, for example: https://yourapp.com/api/PATH

        To avoid hardcoding URLs, you can define a `BASE_URL` constant and set it based on the environment.
        A common pattern is to check an environment variable that only exists in production:

        ```javascript
        const BASE_URL = process.env.VERCEL_URL
          ? `https://${process.env.VERCEL_URL}`
          : `http://localhost:3000`

        const { workflowRunId } = await client.trigger({
            url: `${BASE_URL}/api/workflow`,
            retries: 3
        });
        ```
        </Tip>

    </Step>

</Steps>

# Local Tunnel
Source: https://upstash.com/docs/workflow/howto/local-development/local-tunnel

Upstash Workflow requires your application to be publicly accessible in production.
The recommended approach is running the development server we provide locally and work with local addresses.
An alternative is to making your application publibly accessible so that you can work with the managed Upstash Workflow servers.

The easiest way to make a local URL publically available is [ngrok](https://ngrok.com), a free tunneling service.

Create an account on [dashboard.ngrok.com/signup](https://dashboard.ngrok.com/signup) and follow the [setup instructions](https://dashboard.ngrok.com/get-started/setup) to download the ngrok CLI and connect your account. This process takes only a few minutes and is totally free.

You can connect your account like this:

<Tabs>
    <Tab title="macOS">
          <img />
    </Tab>

    <Tab title="Windows">
          <img />
    </Tab>
</Tabs>

Once you have installed the ngrok CLI, add your ngrok-issued auth token like this:

```bash Terminal
ngrok config add-authtoken <YOUR-AUTH-TOKEN>
```

and replace `<YOUR-AUTH-TOKEN>` with your actual auth token.

### Start the tunnel

Make your local server available publically by running the following command:

```bash
ngrok http <PORT>
```

for example, if your Next.js server is running on port `3000`, the command is:

```bash
ngrok http 3000
```

The output will look something like this:

```plaintext
Session Status                online
Account                       <YOUR-NAME> (Plan: Free)
Version                       3.1.0
Region                        Europe (eu)
Latency                       -
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://e02f-2a02-810d-af40-5284-b139-58cc-89df-b740.eu.ngrok.io -> http://localhost:3000
Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00
```

The long URL in the `Forwarding` line serves the same purpose as your localhost URL, the only difference being that it is publically accessible. We need this URL to make our workflow available to QStash for local development, either as the `baseUrl` parameter or the `UPSTASH_WORKFLOW_URL` environment variable (both options provide the same functionality).

Note: The `UPSTASH_WORKFLOW_URL` environment variable is only necessary for local development. In production, the `baseUrl` parameter is automatically set and can be omitted.

<Tip>
  Ensure that the port of your local server matches the one you're using with ngrok. For example, if your server is
  running on port 8080, use `ngrok http 8080`.
</Tip>

# Middlewares
Source: https://upstash.com/docs/workflow/howto/middlewares

Middlewares allow you to intercept and respond to workflow lifecycle events and debug messages. They're useful for logging, monitoring, error tracking, and custom integrations.

## Overview

A middleware can hook into:
* **Lifecycle Events**: Run started, run completed, before/after step execution
* **Debug Events**: Errors, warnings, and info messages

## Built-in Middleware

Upstash Workflow provides a built-in logging middleware that you can use out of the box:

```typescript
import { serve } from "@upstash/workflow/nextjs";
import { loggingMiddleware } from "@upstash/workflow";

export const { POST } = serve(
  async (context) => {
    await context.run("step-1", () => {
      return "Hello World";
    });
  },
  {
    middlewares: [loggingMiddleware]
  }
);
```

The logging middleware outputs detailed execution logs to your application's console, including:
* When workflow runs start and complete
* Before and after each step execution
* Error, warning, and info messages

## Creating Custom Middleware

You can create your own middleware by instantiating a `WorkflowMiddleware` class.

### Using Direct Callbacks

The simplest way to create a middleware is by providing callbacks directly:

```typescript
import { WorkflowMiddleware } from "@upstash/workflow";

const customMiddleware = new WorkflowMiddleware({
  name: "custom-logger",
  callbacks: {
    // Lifecycle events
    runStarted: async ({ context }) => {
      console.log(`Workflow ${context.workflowRunId} started`);
    },
    beforeExecution: async ({ context, stepName }) => {
      console.log(`Executing step: ${stepName}`);
    },
    afterExecution: async ({ context, stepName, result }) => {
      console.log(`Step ${stepName} completed with result:`, result);
    },
    runCompleted: async ({ context, result }) => {
      console.log(`Workflow ${context.workflowRunId} completed:`, result);
    },

    // Debug events
    onError: async ({ workflowRunId, error }) => {
      console.error(`Error in ${workflowRunId}:`, error);
    },
    onWarning: async ({ workflowRunId, warning }) => {
      console.warn(`Warning in ${workflowRunId}:`, warning);
    },
    onInfo: async ({ workflowRunId, info }) => {
      console.info(`Info from ${workflowRunId}:`, info);
    }
  }
});
```

### Using Init Function

For middlewares that need to initialize resources (like database connections or external clients), use the `init` pattern:

```typescript
import { WorkflowMiddleware } from "@upstash/workflow";

const databaseMiddleware = new WorkflowMiddleware({
  name: "database-logger",
  init: async () => {
    // Initialize your resources
    const db = await connectToDatabase();

    // Return the callbacks that use the initialized resources
    return {
      runStarted: async ({ context }) => {
        await db.insert({ workflowRunId: context.workflowRunId, status: 'started' });
      },
      runCompleted: async ({ context, result }) => {
        await db.update({ workflowRunId: context.workflowRunId, status: 'completed', result });
      },
      onError: async ({ workflowRunId, error }) => {
        await db.insert({ workflowRunId, level: 'error', message: error.message });
      }
    };
  }
});
```

## Event Types

### Lifecycle Events

<ParamField path="runStarted" type="function">
  Called when a workflow run begins.

  **Parameters:**
  * `context`: The workflow context

  ```typescript
  runStarted: async ({ context }) => {
    // Handle run start
  }
  ```
</ParamField>

<ParamField path="beforeExecution" type="function">
  Called before each step executes.

  **Parameters:**
  * `context`: The workflow context
  * `stepName`: Name of the step about to execute

  ```typescript
  beforeExecution: async ({ context, stepName }) => {
    // Handle step start
  }
  ```
</ParamField>

<ParamField path="afterExecution" type="function">
  Called after each step completes.

  **Parameters:**
  * `context`: The workflow context
  * `stepName`: Name of the completed step
  * `result`: The result returned by the step

  ```typescript
  afterExecution: async ({ context, stepName, result }) => {
    // Handle step completion
  }
  ```
</ParamField>

<ParamField path="runCompleted" type="function">
  Called when the entire workflow run finishes.

  **Parameters:**
  * `context`: The workflow context
  * `result`: The final result of the workflow

  ```typescript
  runCompleted: async ({ context, result }) => {
    // Handle run completion
  }
  ```
</ParamField>

### Debug Events

<ParamField path="onError" type="function">
  Called when an error occurs.

  **Parameters:**
  * `workflowRunId`: The workflow run ID (optional)
  * `error`: The error object

  ```typescript
  onError: async ({ workflowRunId, error }) => {
    // Handle error
  }
  ```
</ParamField>

<ParamField path="onWarning" type="function">
  Called when a warning is logged.

  **Parameters:**
  * `workflowRunId`: The workflow run ID (optional)
  * `warning`: The warning message

  ```typescript
  onWarning: async ({ workflowRunId, warning }) => {
    // Handle warning
  }
  ```
</ParamField>

<ParamField path="onInfo" type="function">
  Called when an info message is logged.

  **Parameters:**
  * `workflowRunId`: The workflow run ID (optional)
  * `info`: The info message

  ```typescript
  onInfo: async ({ workflowRunId, info }) => {
    // Handle info
  }
  ```
</ParamField>

## Examples

### Error Tracking Middleware

Send errors to an external monitoring service:

```typescript
import { WorkflowMiddleware } from "@upstash/workflow";

const errorTrackingMiddleware = new WorkflowMiddleware({
  name: "error-tracking",
  callbacks: {
    onError: async ({ workflowRunId, error }) => {
      await fetch("https://your-monitoring-service.com/errors", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          workflowRunId,
          error: error.message,
          stack: error.stack,
          timestamp: new Date().toISOString()
        })
      });
    }
  }
});
```

### Multiple Middlewares

You can use multiple middlewares together:

```typescript
import { serve } from "@upstash/workflow/nextjs";
import { loggingMiddleware } from "@upstash/workflow";

export const { POST } = serve(
  async (context) => {
    // Your workflow logic
  },
  {
    middlewares: [
      loggingMiddleware,
      errorTrackingMiddleware,
      performanceMiddleware
    ]
  }
);
```

Middlewares are executed in the order they're provided in the array.

# Migration Guide
Source: https://upstash.com/docs/workflow/howto/migrations

This guide covers migration between different versions of Upstash Workflow.

<AccordionGroup>
  <Accordion title="January 2026 - Major Release 1.0.0" defaultOpen>
    In January 2026, we released 1.0.0 version of the TypeScript SDK with several breaking changes to improve the developer experience, reduce bundle size, and simplify configuration.

    ## Agents API → Separate Package

    The Agents API has been moved to a separate package to remove the AI SDK dependency from the core workflow package.

    ### Migration Steps

    1. Install the new package:

    ```bash
    npm install @upstash/workflow-agents
    ```

    2. Update your imports:

    ```typescript
    // Old
    import { serve } from "@upstash/workflow/nextjs";

    export const { POST } = serve(async (context) => {
      const model = context.agents.openai('gpt-3.5-turbo');
      const agent = context.agents.agent({ ... });
      const task = context.agents.task({ ... });
    });

    // New
    import { serve } from "@upstash/workflow/nextjs";
    import { agentWorkflow } from "@upstash/workflow-agents";

    export const { POST } = serve(async (context) => {
      const agents = agentWorkflow(context)

      const model = agents.openai('gpt-3.5-turbo');
      const agent = agents.agent({ ... });
      const task = agents.task({ ... });
    });
    ```

    See [Agents documentation](/docs/workflow/agents/overview) for more details.

    ## Removed `keepTriggerConfig` and `useFailureFunction`

    These parameters are no longer needed in `client.trigger()` as both are now `true` by default.

    ### Migration Steps

    Simply remove these parameters from your trigger calls:

    ```typescript
    // Old
    const { workflowRunId } = await client.trigger({
      url: "https://your-app.com/api/workflow",
      retries: 3,
      keepTriggerConfig: true,
      useFailureFunction: true
    });

    // New
    const { workflowRunId } = await client.trigger({
      url: "https://your-app.com/api/workflow",
      retries: 3
    });
    ```

    Configuration passed to `trigger()` now automatically applies to the entire workflow.

    ## Configuration Moved from `serve` to `trigger`

    The `retries`, `flowControl`, `retryDelay`, and `failureUrl` options have been removed from `serve()` and should now be passed in `client.trigger()`.

    ### Migration Steps

    Move configuration from serve options to trigger:

    ```typescript
    // Old
    export const { POST } = serve(
      async (context) => { ... },
      {
        retries: 3,
        retryDelay: "1000 * (1 + retried)",
        flowControl: { key: "my-key", rate: 10 }
      }
    );

    // Trigger call
    await client.trigger({ url: "..." });

    // New
    export const { POST } = serve(
      async (context) => { ... }
      // No configuration here anymore
    );

    // Configuration in trigger call
    await client.trigger({
      url: "...",
      retries: 3,
      retryDelay: "1000 * (1 + retried)",
      flowControl: { key: "my-key", rate: 10 }
    });
    ```

    This change makes it easier to configure different behavior for different workflow runs of the same endpoint.

    <Note>
      In the Python SDK, these options remain in the `serve` decorator as they were before.
    </Note>

    ## Removed `stringifyBody` from `context.call` and `context.invoke`

    The `stringifyBody` parameter has been removed. The `body` parameter now expects a string.

    ### Migration Steps

    Update your call and invoke methods to use `JSON.stringify()`:

    ```typescript
    // Old
    const result = await context.call("call-api", {
      url: "https://api.example.com/endpoint",
      method: "POST",
      body: { key: "value" },
      stringifyBody: true
    });

    // New
    const result = await context.call("call-api", {
      url: "https://api.example.com/endpoint",
      method: "POST",
      body: JSON.stringify({ key: "value" })
    });
    ```

    The same applies to `context.invoke()`:

    ```typescript
    // Old
    await context.invoke("invoke-workflow", {
      workflow: otherWorkflow,
      body: { key: "value" },
      stringifyBody: true
    });

    // New
    await context.invoke("invoke-workflow", {
      workflow: otherWorkflow,
      body: JSON.stringify({ key: "value" })
    });
    ```

    ## Logger → Middleware System

    The logging system has been replaced with a more flexible middleware system.

    ### Migration Steps

    Replace the old logger with the new middleware:

    ```typescript
    // Old
    // Logging was automatic or controlled via verbose option
    export const { POST } = serve(
      async (context) => { ... },
      { verbose: true }
    );

    // New
    import { loggingMiddleware } from "@upstash/workflow";

    export const { POST } = serve(
      async (context) => { ... },
      {
        middlewares: [loggingMiddleware]
      }
    );
    ```

    You can also create custom middlewares for more control. See [Middlewares documentation](/docs/workflow/howto/middlewares) for details.

    ## Removed `onStepFinish`

    The `onStepFinish` callback has been removed. Use middlewares instead.

    ### Migration Steps

    Replace `onStepFinish` with a custom middleware:

    ```typescript
    // Old
    export const { POST } = serve(
      async (context) => { ... },
      {
        onStepFinish: (stepName, result) => {
          console.log(`Step ${stepName} finished with:`, result);
        }
      }
    );

    // New
    import { WorkflowMiddleware } from "@upstash/workflow";

    const stepFinishMiddleware = new WorkflowMiddleware({
      name: "step-finish",
      callbacks: {
        afterExecution: async ({ stepName, result }) => {
          console.log(`Step ${stepName} finished with:`, result);
        }
      }
    });

    export const { POST } = serve(
      async (context) => { ... },
      {
        middlewares: [stepFinishMiddleware]
      }
    );
    ```

    See [Middlewares documentation](/docs/workflow/howto/middlewares) for more details.

  </Accordion>

  <Accordion title="October 2024 - Migrate from @upstash/qstash">
    In October 2024, we released a new SDK, `@upstash/workflow`, for Upstash Workflow, separating its development from the QStash SDK. Although Upstash Workflow is built on QStash, our goal is to improve the developer experience and support with a dedicated SDK. Development for Upstash Workflow will occur in `@upstash/workflow`, and Workflow-related imports will be removed from `@upstash/qstash` in future releases.

    If you started using Upstash Workflow with `@upstash/qstash`, you will need to migrate to `@upstash/workflow`. We have made some backward-incompatible changes, but we aim to make the transition as smooth as possible.
    In this guide, we will explain the changes you may need to make for migration.

    ### Install `@upstash/workflow`

    First, we will need to install the new package with:

    <Tabs>
      <Tab title="npm">
      ```bash
      npm install @upstash/workflow
      ```
      </Tab>
      <Tab title="pnpm">
      ```bash
      pnpm install @upstash/workflow
      ```
      </Tab>
      <Tab title="bun">
      ```bash
      bun add @upstash/workflow
      ```
      </Tab>
    </Tabs>

    If you were using `@upstash/qstash` only for workflow, you can uninstall it from your project.

    ### Serve methods

    You will need to change the imports from `@upstash/qstash` to @upstash/workflow:

    ```ts
    // old
    import { serve } from "@upstash/qstash/nextjs"

    // new
    import { serve } from "@upstash/workflow/nextjs"
    ```

    We have updated what our `serve` methods return. We made this change to make it
    easier to extend the API in the future.

    For instance, Next.js method changed like this:

    ```javascript
    // old
    export const POST = serve(...);

    // new
    export const { POST } = serve(...);
    ```

    We kept the `serve` method of `Hono` the same. The rest are updated in a similar way.
    See [the quickstarts](/docs/workflow/quickstarts/platforms) for the new way `serve`
    should be used.

    Additionally, `@upstash/workflow/nuxt` import is removed. You should use `@upstash/workflow/h3`
    instead. This change was made because `nuxt` uses `h3` under the hood and our `serve` method
    for `nuxt` can work with any project using `h3`.

    ### Updating `context.call`

    If you were using [`context.call` method](/docs/workflow/basics/context#context-call) in your workflow, you will need to change
    how it's called and what it returns. Here is what the change looks like:

    ```javascript
    // old
    const result = await context.call("call step", "<call-url>", "POST", ...)

    // new
    const {
      status,  // response status
      headers, // response headers
      body     // response body
    } = await context.call("call step", {
      url: "<call-url>",
      method: "POST",
      ...
    })
    ```

    In the old version, we only returned the response body. Also, if the request
    to the url failed, [the workflow run would fail](/docs/workflow/howto/failures).

    In the new version, we update how the parameters are passed to the `context.call`.
    Additionally, we change the fail behavior: if the request fails, it doesn't make the
    workflow fail. Instead, the status and the body is simply returned and workflow
    continues as usual.

    If you have ongoing workflow runs which call `context.call` during your transition,
    `status` and `headers` fields may not be available in these old runs. After your
    transition, all workflow runs will have all three fields.

    ### Renaming Errors

    The errors in Workflow were renamed from `QStashWorkflowError` and `QStashWorkflowAbort` to `WorkflowError` and `WorkflowAbort`.

  </Accordion>
</AccordionGroup>

# Select a Region
Source: https://upstash.com/docs/workflow/howto/multi-region

## Overview

Upstash Workflow operates on top of QStash, which is available in two distinct regions: **EU region** and **US region**. Each region is completely independent with its own infrastructure, pricing, resources, and workflow runs.

## Regional URLs

* **EU Region**: `https://qstash-eu-central-1.upstash.io`, or `https://qstash.upstash.io`
* **US Region**: `https://qstash-us-east-1.upstash.io`

## Key Concepts

Each region maintains:
* Usage in each region is tracked and billed independently
* Workflow runs, events, and configurations are region-specific
* Each region has its own API tokens and signing keys

### Migration Between Regions

If you don't have any active resources (active workflow runs, schedules, url groups etc), you can simply update your environment variables with the new region to migrate. If you have active resources, you will need to migrate more gracefully, as described below.

You can migrate your Workflow resources from one region to another using the Upstash Console:

1. Navigate to the [Workflow tab on Upstash Console](https://console.upstash.com/workflow)
2. Click the **Migrate** button
3. Follow the guided migration process

<img />

The migration tool will:
* Help you set up migration-mode environment variables
* Copy and update your QStash resources (schedules, url groups, queues)

Your workflow logs or DLQ aren't part of the migration. They will remain in the old region.

<Info>
After migration, your app will be able to handle requests from both regions simultaneously to ensure a smooth transition.
</Info>

## Operating Modes

Workflow SDK supports two modes of operation:

### Single-Region Mode (Default)

When `QSTASH_REGION` environment variable is **not set**, the SDK operates in single-region mode:

* Uses `QSTASH_TOKEN` and `QSTASH_URL` (or defaults to EU region)
* All workflow triggers are sent through the configured region
* Incoming workflow requests are verified using default signing keys

```bash
# Single-region configuration (EU)
QSTASH_URL="https://qstash.upstash.io"
QSTASH_TOKEN="your_eu_token"
QSTASH_CURRENT_SIGNING_KEY="your_eu_current_key"
QSTASH_NEXT_SIGNING_KEY="your_eu_next_key"
```

### Migration Mode

When `QSTASH_REGION` is set to `US_EAST_1` or `EU_CENTRAL_1`, the SDK enables migration mode:

* Uses region-specific credentials for the primary region (`QSTASH_REGION`)
* Automatically handles region detection for incoming workflow requests
* Supports receiving workflow calls from multiple regions simultaneously

<Note>
  If a workflow run was started in one region, all its steps will execute in that region.
</Note>

Environment variables:

```bash
# Migration mode configuration with US as primary
QSTASH_REGION="US_EAST_1"

US_EAST_1_QSTASH_URL="https://qstash-us-east-1.upstash.io"
US_EAST_1_QSTASH_TOKEN="your_us_token"
US_EAST_1_QSTASH_CURRENT_SIGNING_KEY="your_us_current_key"
US_EAST_1_QSTASH_NEXT_SIGNING_KEY="your_us_next_key"

EU_CENTRAL_1_QSTASH_URL="https://qstash-eu-central-1.upstash.io"
EU_CENTRAL_1_QSTASH_TOKEN="your_eu_token"
EU_CENTRAL_1_QSTASH_CURRENT_SIGNING_KEY="your_eu_current_key"
EU_CENTRAL_1_QSTASH_NEXT_SIGNING_KEY="your_eu_next_key"
```

<Warning>
Migration mode relies on environment variables being available via `process.env`. It won't work on platforms where `process.env` is not available, such as Cloudflare Workers.
</Warning>

## SDK Requirements

Migration support requires:
* `@upstash/workflow` >= 1.1.0
* `@upstash/qstash` >= 2.9.0

Update your dependencies:

```bash
npm install @upstash/workflow@latest @upstash/qstash@latest
```

# Parallel Runs
Source: https://upstash.com/docs/workflow/howto/parallel-runs

<Note>
  This feature is not yet available in
  [workflow-py](https://github.com/upstash/workflow-py). See our
  [Roadmap](/docs/workflow/roadmap) for feature parity plans and
  [Changelog](/docs/workflow/changelog) for updates.
</Note>

Just like you can execute multiple JavaScript promises at the same time using `Promise.all`, you can run multiple workflow steps at the same time:

```typescript
const [result1, result2, result3] =
  await Promise.all([
    ctx.run("parallel-step-1", async () => { ... }),
    ctx.run("parallel-step-2", async () => { ... }),
    ctx.run("parallel-step-3", async () => { ... }),
  ])
```

In a complete code example, your workflow could look like this:

```typescript app/api/workflow/route.ts
import { serve } from "@upstash/workflow/nextjs";
import { checkInventory, brewCoffee, printReceipt } from "@/utils";

export const { POST } = serve(async (ctx) => {
  const [coffeeBeansAvailable, cupsAvailable, milkAvailable] =
    await Promise.all([
      ctx.run("check-coffee-beans", () => checkInventory("coffee-beans")),
      ctx.run("check-cups", () => checkInventory("cups")),
      ctx.run("check-milk", () => checkInventory("milk")),
    ]);

  // If all ingedients available, brew coffee
  if (coffeeBeansAvailable && cupsAvailable && milkAvailable) {
    const price = await ctx.run("brew-coffee", async () => {
      return await brewCoffee({ style: "cappuccino" });
    });

    await printReceipt(price);
  }
});
```

After running your workflow, your dashboard shows each step in detail:

  <img />

# Read-only Token
Source: https://upstash.com/docs/workflow/howto/readonly-token

You can use a read-only version of your token to safely share it with your team or AI agents.
This token has access to your logs, messages, schedules, and other resources, but it cannot be used to publish messages, create new resources such as schedules or URL Groups, or modify existing resources.

You can get your read-only token from the dashboard by clicking "Read-only token" in the Quickstart section:

  <img />

<Info>
Note that a read-only token can still access sensitive data such as logs and messages.

If you want to share your token without exposing sensitive data, you can use the ["Redact Fields"](/docs/workflow/howto/redact-fields) feature to hide sensitive data from the logs and resources.
</Info>

# Realtime Quickstart
Source: https://upstash.com/docs/workflow/howto/realtime/basic

[**Upstash Realtime**](/docs/realtime/overall/quickstart) lets you emit events from your workflow and subscribe to them in real-time on your frontend.

## How It Works

Upstash Realtime is powered by Upstash Redis and provides a clean, 100% type-safe API for publishing and subscribing to events:

* Your frontend can subscribe to events
* When you **emit** an event, it's instantly delivered to live subscribers on the frontend
* You can also replay events that happened in the past

This guide shows you how to integrate Upstash Workflow with Upstash Realtime to display real-time progress updates in your frontend.

## Setup

### 1. Install Packages

```bash
npm install @upstash/workflow @upstash/realtime @upstash/redis zod
```

### 2. Configure Upstash Realtime

Create a Realtime instance in `lib/realtime.ts`:

```typescript title="lib/realtime.ts"
import { InferRealtimeEvents, Realtime } from "@upstash/realtime";
import { Redis } from "@upstash/redis";
import z from "zod/v4";

const redis = Redis.fromEnv();

const schema = {
  workflow: {
    runFinish: z.object({}),
    stepFinish: z.object({
      stepName: z.string(),
      result: z.unknown().optional(),
    }),
  },
};

export const realtime = new Realtime({ schema, redis });
export type RealtimeEvents = InferRealtimeEvents<typeof realtime>;
```

### 3. Create a Realtime Middleware

Create a custom middleware that will emit events to Realtime at `lib/middleware.ts`:

```typescript title="lib/middleware.ts"
import { WorkflowMiddleware } from "@upstash/workflow";
import { realtime } from "./realtime";

export const realtimeMiddleware = new WorkflowMiddleware({
  name: "realtime-events",
  callbacks: {
    afterExecution: async ({ context, stepName, result }) => {
      const channel = realtime.channel(context.workflowRunId);
      await channel.emit("workflow.stepFinish", {
        stepName,
        result,
      });
    },
    runCompleted: async ({ context }) => {
      const channel = realtime.channel(context.workflowRunId);
      await channel.emit("workflow.runFinish", {});
    },
  },
});
```

**Key points:**

* The `afterExecution` callback is triggered after each workflow step completes
* The `runCompleted` callback is triggered when the entire workflow finishes
* We use `context.workflowRunId` to create a unique channel for each workflow run
* Events are automatically emitted without needing to manually call emit inside your workflow steps

### 4. Create a Realtime Endpoint

Create an API route at `app/api/realtime/route.ts` to handle Realtime connections:

```typescript title="app/api/realtime/route.ts"
import { handle } from "@upstash/realtime";
import { realtime } from "@/lib/realtime";

export const GET = handle({ realtime });
```

This endpoint enables Server-Sent Events (SSE) connections for real-time updates.

### 5. Add the Realtime Provider

Wrap your application in the `RealtimeProvider` by updating your root layout at `app/layout.tsx`:

```tsx title="app/layout.tsx"
"use client";

import { RealtimeProvider } from "@upstash/realtime/client";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html>
      <body>
        <RealtimeProvider>{children}</RealtimeProvider>
      </body>
    </html>
  );
}
```

### 6. Create a Typed Client Hook

Create a typed `useRealtime` hook at `lib/realtime-client.ts`:

```typescript title="lib/realtime-client.ts"
"use client";

import { createRealtime } from "@upstash/realtime/client";
import type { RealtimeEvents } from "./realtime";

export const { useRealtime } = createRealtime<RealtimeEvents>();
```

***

## Building the Workflow

### 1. Create the Workflow Endpoint

Create your workflow at `app/api/workflow/route.ts`:

```typescript title="app/api/workflow/route.ts"
import { serve } from "@upstash/workflow/nextjs";
import { realtimeMiddleware } from "@/lib/middleware";

type WorkflowPayload = {
  userId: string;
  action: string;
};

export const { POST } = serve<WorkflowPayload>(
  async (context) => {
    const { userId, action } = context.requestPayload;

    await context.run("validate-data", async () => {
      return { valid: true, userId, action };
    });

    await context.run("process-action", async () => {
      // Your business logic here
      return { processed: true, userId, action };
    });

    return { success: true, workflowRunId: context.workflowRunId };
  },
  {
    middlewares: [realtimeMiddleware],
  }
);
```

**Key points:**

* Import the `realtimeMiddleware` from `@/lib/middleware`
* Pass the middleware in the `middlewares` array to the `serve` function
* The middleware automatically emits events after each step and when the workflow completes

### 2. Create a Trigger Endpoint

Create an endpoint to trigger workflows at `app/api/trigger/route.ts`:

```typescript title="app/api/trigger/route.ts"
import { NextRequest, NextResponse } from "next/server";
import { Client } from "@upstash/workflow";

export const workflowClient = new Client({
  token: process.env.QSTASH_TOKEN,
  baseUrl: process.env.QSTASH_URL,
});

export async function POST(request: NextRequest) {
  const workflowUrl = `${request.nextUrl.origin}/api/workflow`;

  const { workflowRunId } = await workflowClient.trigger({
    url: workflowUrl,
    body: {
      userId: "user-123",
      action: "process-data",
    },
  });

  return NextResponse.json({ workflowRunId });
}
```

***

## Building the Frontend

### 1. Create a Custom Hook

Create a React hook to manage the Realtime subscription at `hooks/useWorkflow.ts`:

```typescript
"use client";

import { useRealtime } from "@/lib/realtime-client";
import { useState, useCallback } from "react";

interface WorkflowStep {
  stepName: string;
  result?: unknown;
}

export function useWorkflow() {
  const [workflowRunId, setWorkflowRunId] = useState<string | null>(null);
  const [steps, setSteps] = useState<WorkflowStep[]>([]);
  const [isRunFinished, setIsRunFinished] = useState(false);

  useRealtime({
    enabled: Boolean(workflowRunId),
    channels: workflowRunId ? [workflowRunId] : [],
    events: ["workflow.stepFinish", "workflow.runFinish"],
    onData({ event, data }) {
      if (event === "workflow.stepFinish") {
        setSteps((prev) => [...prev, data]);
      }

      if (event === "workflow.runFinish") {
        setIsRunFinished(true);
      }
    },
  });

  const trigger = () => {
    setSteps([]);
    setIsRunFinished(false);

    const response = await fetch("/api/trigger", {
      method: "POST",
    });

    const data = await response.json();
    setWorkflowRunId(data.workflowRunId);
  };

  return {
    trigger,
    workflowRunId,
    steps,
    isRunFinished,
  };
}
```

**Key features:**

* Subscribe to multiple events using the `events` array: `["workflow.stepFinish", "workflow.runFinish"]`
* The hook manages both triggering the workflow and subscribing to updates
* Type-safe event handling with TypeScript

### 2. Use the Hook in Your Component

```tsx
"use client";

import { useWorkflow } from "@/hooks/useWorkflow";

export default function WorkflowPage() {
  const { trigger, steps, isRunFinished } = useWorkflow();

  return (
    <div>
      <button onClick={trigger}>Click to Trigger Workflow</button>

      {isRunFinished && <p>✅ Workflow Finished!</p>}

      <p>Workflow Steps:</p>

      {steps.map((step, index) => (
        <div key={index}>
          <strong>{step.stepName}</strong>
          {Boolean(step.result) && <span>: {JSON.stringify(step.result)}</span>}
        </div>
      ))}
    </div>
  );
}
```

## How It All Works Together

1. **User triggers workflow**: The frontend calls `/api/trigger`, which returns a `workflowRunId`
2. **Frontend subscribes**: Using the `workflowRunId`, the frontend subscribes to the Realtime channel
3. **Workflow executes**: The workflow runs as a background job, emitting events at each step
4. **Real-time updates**: As the workflow emits events, they're instantly delivered to the frontend via Server-Sent Events

## Full Example

For a complete working example with all steps, error handling, and UI components, check out the [Upstash Realtime example on GitHub](https://github.com/upstash/workflow-js/tree/main/examples/upstash-realtime).

## Next Steps

* Learn about [human-in-the-loop workflows with Realtime](./human-in-the-loop)
* Explore [Realtime features](/docs/realtime/overall/quickstart)
* Check out [Workflow configuration options](/docs/workflow/howto/configure)

# Human-in-the-Loop
Source: https://upstash.com/docs/workflow/howto/realtime/human-in-the-loop

Some workflows require human approval or input before proceeding. When combined with [**Upstash Realtime**](/docs/realtime/overall/quickstart), you can create interactive workflows that pause for user input and provide real-time feedback to your frontend during the entire process.

This guide shows you how to implement a human-in-the-loop workflow pattern with real-time updates using Upstash Workflow and Upstash Realtime.

## How It Works

In a human-in-the-loop workflow:

1. The workflow executes initial steps and emits progress events
2. The workflow pauses at a specific point using [`context.waitForEvent()`](/docs/workflow/features/wait-for-event)
3. A "waiting for input" event is emitted to notify the frontend
4. The user makes a decision in the frontend (approve/reject)
5. The frontend calls an API to notify the workflow using [`client.notify()`](/docs/workflow/basics/client/notify)
6. The workflow resumes with the user's decision
7. An "input resolved" event is emitted so the frontend can update its UI
8. The workflow continues and completes based on the decision

## Prerequisites

* An Upstash account with:
  * A QStash project for workflows
  * A Redis database for Realtime
* Next.js application set up
* Completed the [basic real-time workflow setup](./basic)

## Event Types

For human-in-the-loop workflows, extend your schema in `lib/realtime.ts` with these additional event types:

```typescript {8-14}
const schema = {
  workflow: {
    runFinish: z.object({}),
    stepFinish: z.object({
      stepName: z.string(),
      result: z.unknown().optional(),
    }),
    waitingForInput: z.object({
      eventId: z.string(),
      message: z.string(),
    }),
    inputResolved: z.object({
      eventId: z.string(),
    }),
  },
};
```

The new event types are:

* **`waitingForInput`**: Emitted when the workflow pauses and needs user input
* **`inputResolved`**: Emitted when the user provides input, so the frontend knows to clear the waiting state

## Create the Realtime Middleware

Create a custom middleware that will emit events to Realtime at `lib/middleware.ts`:

```typescript title="lib/middleware.ts"
import { WorkflowMiddleware } from "@upstash/workflow";
import { realtime } from "./realtime";

export const realtimeMiddleware = new WorkflowMiddleware({
  name: "realtime-events",
  callbacks: {
    beforeExecution: async ({ context, stepName }) => {
      const channel = realtime.channel(context.workflowRunId);

      // Detect wait-for-event steps and emit waitingForInput
      if (stepName === "wait-for-approval") {
        await channel.emit("workflow.waitingForInput", {
          eventId: `approval-${context.workflowRunId}`,
          message: `Waiting for approval`,
        });
      }
    },
    afterExecution: async ({ context, stepName, result }) => {
      const channel = realtime.channel(context.workflowRunId);

      // Emit inputResolved after wait-for-event steps complete
      if (stepName === "wait-for-approval") {
        await channel.emit("workflow.inputResolved", {
          eventId: `approval-${context.workflowRunId}`,
        });
      }

      // Emit stepFinish for all steps
      await channel.emit("workflow.stepFinish", {
        stepName,
        result,
      });
    },
    runCompleted: async ({ context }) => {
      const channel = realtime.channel(context.workflowRunId);
      await channel.emit("workflow.runFinish", {});
    },
  },
});
```

**Key points:**

* The middleware handles all realtime event emissions automatically
* `beforeExecution`: Detects wait-for-event steps by checking the stepName and emits `workflow.waitingForInput`
* `afterExecution`: Emits `workflow.inputResolved` for wait steps and `workflow.stepFinish` for all steps
* `runCompleted`: Emits `workflow.runFinish` when the workflow finishes
* All emission logic is centralized in the middleware, keeping workflow code clean

## Building the Workflow

### 1. Create the Workflow Endpoint

Create your workflow at `app/api/workflow/human-in-loop/route.ts`:

```typescript title="app/api/workflow/human-in-loop/route.ts"
import { serve } from "@upstash/workflow/nextjs";
import { realtimeMiddleware } from "@/lib/middleware";

type WorkflowPayload = {
  userId: string;
  action: string;
};

export const { POST } = serve<WorkflowPayload>(
  async (context) => {
    const { userId, action } = context.requestPayload;

    // Step 1: Initial Processing
    await context.run("initial-processing", async () => {
      // Your processing logic
      return {
        preprocessed: true,
        userId,
        action,
        requiresApproval: true,
      };
    });

    // Step 2: Wait for Human Approval
    const eventId = `approval-${context.workflowRunId}`;

    const { eventData, timeout } = await context.waitForEvent<{
      approved: boolean;
    }>("wait-for-approval", eventId, { timeout: "5m" });

    // Handle timeout
    if (timeout) {
      return { success: false, reason: "timeout" };
    }

    const status = eventData.approved ? "approved" : "rejected";

    // Step 3: Process based on approval
    await context.run(`process-${status}`, async () => {
      return {
        status,
        processedAt: Date.now(),
        action,
        userId,
      };
    });

    // Step 4: Finalize (only if approved)
    if (eventData.approved) {
      // Additional steps...
    }

    return {
      success: true,
      approved: eventData.approved,
      workflowRunId: context.workflowRunId,
    };
  },
  {
    middlewares: [realtimeMiddleware],
  }
);
```

**Key patterns:**

1. **Middleware for all events**: The `realtimeMiddleware` automatically handles all realtime event emissions by detecting wait-for-event steps through the stepName
2. **Step name detection**: The middleware checks if `stepName === "wait-for-approval"` to know when to emit `waitingForInput` and `inputResolved` events
3. **Unique event IDs**: Use a unique `eventId` (like `approval-${workflowRunId}`) to identify which approval request this is
4. **Timeout handling**: Always handle the timeout case when waiting for events

### 2. Create the Notify Endpoint

Create an endpoint at `app/api/notify/route.ts` to handle user input:

```typescript
import { Client } from "@upstash/workflow";
import { NextRequest, NextResponse } from "next/server";

const workflowClient = new Client({
  baseUrl: process.env.QSTASH_URL!,
  token: process.env.QSTASH_TOKEN!,
});

export async function POST(request: NextRequest) {
  const body = await request.json();
  const { eventId, eventData } = body;

  if (!eventId) {
    return NextResponse.json(
      { success: false, error: "eventId is required" },
      { status: 400 }
    );
  }

  // Notify the workflow
  await workflowClient.notify({
    eventId,
    eventData,
  });

  return NextResponse.json({ success: true });
}
```

<Note>
**Preventing Race Conditions**: If you need to trigger a workflow and immediately send it an event, you can use the `workflowRunId` parameter to enable lookback:

```typescript
await workflowClient.notify({
  eventId,
  eventData,
  workflowRunId: "wfr_abc123", // Ensures notification is delivered even if sent before waitForEvent
});
```

Learn more in the [notify documentation](/docs/workflow/basics/client/notify).
</Note>

## Building the Frontend

### 1. Extend the Custom Hook

Extend your hook from the basic example to handle waiting states:

```typescript
"use client";

import { useRealtime } from "@/lib/realtime-client";
import { useState, useCallback } from "react";

interface WorkflowStep {
  stepName: string;
  result?: unknown;
}

interface WaitingState {
  eventId: string;
  message: string;
}

export function useWorkflowWithRealtime() {
  const [workflowRunId, setWorkflowRunId] = useState<string | null>(null);
  const [steps, setSteps] = useState<WorkflowStep[]>([]);
  const [waitingState, setWaitingState] = useState<WaitingState | null>(null);
  const [isTriggering, setIsTriggering] = useState(false);
  const [isRunFinished, setIsRunFinished] = useState(false);

  useRealtime({
    enabled: !!workflowRunId,
    channels: workflowRunId ? [workflowRunId] : [],
    events: [
      "workflow.stepFinish",
      "workflow.runFinish",
      "workflow.waitingForInput",
      "workflow.inputResolved",
    ],
    onData({ event, data }) {
      if (event === "workflow.stepFinish") {
        setSteps((prev) => [
          ...prev,
          {
            stepName: data.stepName,
            result: data.result,
          },
        ]);
      } else if (event === "workflow.runFinish") {
        setIsRunFinished(true);
      } else if (event === "workflow.inputResolved") {
        // Clear waiting state if it matches
        setWaitingState((prev) =>
          prev?.eventId === data.eventId ? null : prev
        );
      } else if (event === "workflow.waitingForInput") {
        setWaitingState({
          eventId: data.eventId,
          message: data.message,
        });
      }
    },
  });

  const trigger = useCallback(async () => {
    setIsTriggering(true);
    setSteps([]);
    setWaitingState(null);
    setIsRunFinished(false);

    const response = await fetch("/api/trigger", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ workflowType: "human-in-loop" }),
    });

    const data = await response.json();
    setWorkflowRunId(data.workflowRunId);
    setIsTriggering(false);
  }, []);

  const continueWorkflow = useCallback(
    async (data: { approved: boolean }) => {
      if (!waitingState) {
        throw new Error("No workflow waiting for input");
      }

      const response = await fetch("/api/notify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          eventId: waitingState.eventId,
          eventData: data,
        }),
      });

      if (!response.ok) {
        throw new Error("Failed to notify workflow");
      }

      // The waiting state will be cleared when we receive inputResolved event
    },
    [waitingState]
  );

  return {
    trigger,
    continueWorkflow,
    isTriggering,
    workflowRunId,
    steps,
    waitingState,
    isRunFinished,
  };
}
```

**Key additions:**

* **`waitingState`**: Tracks when the workflow is waiting for input
* **`continueWorkflow`**: Function to submit user decisions back to the workflow
* **Multiple events subscription**: Uses `events` array to subscribe to multiple event types
* **Input resolved handling**: Clears the waiting state when the workflow receives the user's input

### 2. Use the Hook with Approval UI

```typescript
"use client";

import { useWorkflowWithRealtime } from "@/hooks/useWorkflowWithRealtime";

export default function WorkflowPage() {
  const {
    trigger,
    isTriggering,
    steps,
    isRunFinished,
    waitingState,
    continueWorkflow,
  } = useWorkflowWithRealtime();

  return (
    <div
      style={{
        maxWidth: "600px",
        margin: "40px auto",
        fontFamily: "Arial, sans-serif",
      }}
    >
      <button onClick={trigger} disabled={isTriggering}>
        {isTriggering ? "Starting..." : "Click to Trigger Workflow"}
      </button>

      {isRunFinished && (
        <h3 style={{ marginTop: "20px" }}>✅ Workflow Finished!</h3>
      )}

      {/* Show workflow steps */}
      <h3 style={{ marginTop: "20px" }}>Workflow Steps:</h3>
      <div>
        {steps.map((step, index) => (
          <div key={index}>
            <strong>{step.stepName}</strong>
            {Boolean(step.result) && (
              <span>: {JSON.stringify(step.result)}</span>
            )}
          </div>
        ))}
      </div>

      {/* Show approval UI when waiting for input */}
      {waitingState && (
        <div style={{ marginTop: "20px" }} className="approval-prompt">
          <p>{waitingState.message}</p>
          <p>
            <button onClick={() => continueWorkflow({ approved: true })}>
              Click to Approve
            </button>
          </p>
          <p>
            <button onClick={() => continueWorkflow({ approved: false })}>
              Click to Reject
            </button>
          </p>
        </div>
      )}
    </div>
  );
}
```

## How the Pattern Works

### Timeline of Events

1. **Initial processing**: `stepFinish` event → Frontend shows completed step
2. **Waiting for approval**: `waitingForInput` event → Frontend shows approval buttons
3. **User clicks approve/reject**: Frontend calls `/api/notify`
4. **Workflow resumes**: `inputResolved` event → Frontend hides approval buttons
5. **Processing continues**: More `stepFinish` events as workflow continues
6. **Workflow completes**: `runFinish` event → Frontend shows "Workflow Finished!"

## Benefits

* **Real-time feedback**: Users see exactly when their approval is needed
* **No polling**: Instant updates via Server-Sent Events
* **Timeout handling**: Workflows don't hang indefinitely waiting for input

## Full Example

For a complete working example with all steps, error handling, and full UI components, check out the [Upstash Realtime example on GitHub](https://github.com/upstash/workflow-js/tree/main/examples/upstash-realtime).

## Next Steps

* Review the [basic real-time workflow pattern](./basic)
* Learn about [workflow event handling](/docs/workflow/features/wait-for-event)
* Explore [Realtime features](/docs/realtime/overall/quickstart)
* Check out [workflow failure handling](/docs/workflow/features/failure-callback)

# Redact Private Data
Source: https://upstash.com/docs/workflow/howto/redact-fields

Workflow runs can contain private data that you don't want visible in the Upstash Console or API responses.

Upstash Workflow allows you to redact specific fields so they appear as `REDACTED:<SHA256>` in the dashboard and API. The original values are still used when delivering requests to your workflow endpoint. The SHA256 hash lets you verify the data without revealing the original values.

To redact fields, pass the `redact` option when triggering a workflow run.

Available options:
| Option | Description |
|--|--|
| body | Redact the body of the workflow steps |
| headers | Redact the headers of the workflow steps |
| headers[header_name] | Redact a specific header (e.g., `headers[Authorization]`) |

<Info>
Redaction is one-way. Once a field is redacted, the original value cannot be retrieved from the API or dashboard.
</Info>

<CodeGroup>
```typescript TypeScript
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" });

const { workflowRunId } = await client.trigger({
  url: "https://my-app.com/api/workflow",
  body: { hello: "world" },
  redact: {
    body: true,
    header: ["Authorization"] // or `header: true` to redact all headers
  },
});
```

```python Python
from upstash_workflow import Client

client = Client("<QSTASH_TOKEN>")
client.trigger(
    url="https://my-app.com/api/workflow",
    body={
        "hello": "world",
    },
    redact={
        "body": True,
        "header": ["Authorization"] // or `header: True` to redact all headers
    },
)
```

```bash cURL
curl -XPOST \
    -H 'Authorization: Bearer XXX' \
    -H "Content-Type: application/json" \
    -H "Upstash-Redact-Fields: body, header[Authorization]" \
    -d '{ "hello": "world" }' \
    'https://qstash.upstash.io/v2/publish/https://my-app.com/api/workflow'
```
</CodeGroup>

  <img />

Redaction is configured per workflow run, so you can redact different fields for different runs.

When `body` is redacted, the step outputs in the dashboard and API will show `REDACTED:<SHA256>` instead of the actual values. The workflow still executes with the original data.

If a workflow run fails and moves to the DLQ, the redacted fields remain redacted in the DLQ.
However, when you retry, resume or restart a workflow run from DLQ, Workflow delivers the original values to your endpoint.

# Schedule a Workflow
Source: https://upstash.com/docs/workflow/howto/schedule

You can schedule a workflow to run periodically using a cron definition.

For this feature, you would need to use Upstash QStash's Schedules feature.

## Scheduling a workflow

For example, let's suppose that you have a workflow that creates a backup of some important data daily. Our workflow endpoint might look like this:

To run this endpoint on a schedule, navigate to `Schedules` in your QStash dashboard and click `Create Schedule`:

  <img />

Enter your live endpoint URL, add a CRON expression to define the interval at which your endpoint is called (i.e. every day, every 15 minutes, ...) and click `Schedule`:

  <img />

Your workflow will now run repeatedly at the interval you have defined. For more details on CRON expressions, see our [QStash scheduling documentation](/docs/qstash/features/schedules).

## Programmatically Schedule

In order to massively improve the user experience, many applications send weekly summary reports to their users. These could be weekly analytics summaries or SEO statistics to keep users engaged with the platform.

Let's create a user-specific schedule, sending a first report to each user exactly 7 days after they signed up:

<CodeGroup>

```typescript api/sign-up/route.ts
import { signUp } from "@/utils/auth-utils";
import { Client } from "@upstash/qstash";

const client = new Client({ token: process.env.QSTASH_TOKEN! });

export async function POST(request: Request) {
  const userData: UserData = await request.json();

  // Schedule weekly account summary
  await client.schedules.create({
    scheduleId: `user-summary-${user.email}`,
    destination: "https://<YOUR_APP_URL>/api/send-weekly-summary",
    body: { userId: user.id },
    cron: cron,
  });

  return NextResponse.json(
    { success: true, message: "User registered and summary scheduled" },
    { status: 201 }
  );
}
```

```python main.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from qstash import AsyncQStash
from datetime import datetime, timedelta

app = FastAPI()

client = AsyncQStash("<QSTACK_TOKEN>")

@app.post("/api/sign-up")
async def sign_up(request: Request):
    user_data = await request.json()

    # Simulate user registration
    user = await sign_up(user_data)

    # Calculate the date for the first summary (7 days from now)
    first_summary_date = datetime.now() + timedelta(days=7)

    # Create cron expression for weekly summaries starting 7 days from signup
    cron = f"{first_summary_date.minute} {first_summary_date.hour} * * {first_summary_date.day}"

    # Schedule weekly account summary
    await client.schedule.create_json(
        schedule_id=f"user-summary-{user.email}",
        destination="https://<YOUR_APP_URL>/api/send-weekly-summary",
        body={"userId": user.id},
        cron=cron,
    )

    return JSONResponse(
        content={"success": True, "message": "User registered and summary scheduled"},
        status_code=201,
    )

```

</CodeGroup>

This code will call our workflow every week, starting exactly seven days after a user signs up. Each call to our workflow will contain the respective user's ID.

<Note>
    When creating a per-user schedule, pass a unique `scheduleId` to identify the schedule for better management and observability.
</Note>

# Secure a Workflow
Source: https://upstash.com/docs/workflow/howto/security

To prevent unauthorized access to your workflow endpoint, you can add an authorization layer.
Upstash Workflow supports two approaches:

* **Built-in request verification** (recommended)
* **Custom authorization method**

### Built-in request verification (recommended)

Upstash Workflow provides a built-in mechanism to secure your workflow endpoint by verifying request signatures.
Every request to your endpoint include a valid `Upstash-Signature` header.

How it works:

1. Upstash Workflow automatically adds the `Upstash-Signature` header to every request.
   This signature is generated using your signing keys.

2. When this mechanism is enabled, the SDK verifies that the signature is valid before processing the request.

This ensures that only requests originating from Upstash Workflow are processed.

To enable this verification, set the following environment variables in your application:

```bash .env
QSTASH_CURRENT_SIGNING_KEY=xxxxxxxxx
QSTASH_NEXT_SIGNING_KEY=xxxxxxxxx
```

You can find the values in Upstash Workflow dashboard.

  <img />

<Info>
    For edge cases where environment variables cannot be used, you can explicitly create and pass a `Receiver` object to verify request signatures:

    <CodeGroup>

        ```typescript TypeScript
        import { Receiver } from "@upstash/qstash";
        import { serve } from "@upstash/workflow/nextjs";

        export const { POST } = serve(
          async (context) => { ... },
          {
            receiver: new Receiver({
              currentSigningKey: "<QSTASH_CURRENT_SIGNING_KEY>",
              nextSigningKey: "<QSTASH_NEXT_SIGNING_KEY>",
            }),
          }
        );
        ```

        ```python Python
        from qstash import Receiver

        @serve.post(
            "/api/example",
            receiver=Receiver(
                current_signing_key=os.environ["QSTASH_CURRENT_SIGNING_KEY"],
                next_signing_key=os.environ["QSTASH_NEXT_SIGNING_KEY"],
            ),
        )
        async def example(context: AsyncWorkflowContext[str]) -> None:
            ...

        ```
    </CodeGroup>
</Info>

## Custom Authorization Method

You can implement your own authorization mechanism with Upstash Workflow.

The context object provides access to the initial request headers and payload on every workflow step.
You can use them to pass your custom authentication token to verify the requests.

<CodeGroup>

    ```typescript TypeScript
    import { serve } from "@upstash/workflow/nextjs";

    export const { POST } = serve(
      async (context) => {
        // 👇 Extract Bearer token form the request headers
        const authHeader = context.headers.get("authorization");
        const bearerToken = authHeader?.split(" ")[1];

        // 👇 Use your authentication function to verify the token
        if (!isValid(bearerToken)) {
          console.error("Authentication failed.");
          return;
        }

        // Your workflow steps..
      },
      {
        failureFunction: async () => {
          // 👇 Same auth check for failure function
          const authHeader = context.headers.get("authorization");
          const bearerToken = authHeader?.split(" ")[1];

          if (!isValid(bearerToken)) {
            // ...
          }
        },
      }
    );
    ```

    ```python Python
    from fastapi import FastAPI
    from upstash_workflow.fastapi import Serve
    from upstash_workflow import AsyncWorkflowContext

    app = FastAPI()
    serve = Serve(app)

    @serve.post("/api/example")
    async def example(context: AsyncWorkflowContext[str]) -> None:
        auth_header = context.headers.get("authorization")
        bearer_token = auth_header.split(" ")[1] if auth_header else None

        if not is_valid(bearer_token):
            print("Authentication failed.")
            return

        # Your workflow steps...

    ```

</CodeGroup>

<Warning>
    If you implement custom authorization in your workflow route, you should also include the same authorization check in the failure function.

    The failure function executes independently of the route function, so without this check, unauthorized requests could trigger the failure function
</Warning>

# Start a Run
Source: https://upstash.com/docs/workflow/howto/start

You’ve defined your workflow, and the final step is to trigger the endpoint!

There are two main ways to start your workflow:

### Using `client.trigger` (Recommended)

We recommend using [`client.trigger`](/docs/workflow/basics/client/trigger) to start your workflow.

<CodeGroup>
    ```ts Single Workflow
    import { Client } from "@upstash/workflow";

    const client = new Client({ token: "<QSTASH_TOKEN>" })
    const { workflowRunId } = await client.trigger({
      url: "https://<YOUR_WORKFLOW_ENDPOINT>/<YOUR-WORKFLOW-ROUTE>",
      body: "hello there!",         // optional body
      headers: { ... },             // optional headers
      workflowRunId: "my-workflow", // optional workflow run id
      retries: 3                    // optional retries in the initial request
      delay: "10s"                  // optional delay value
      failureUrl: "https://<YOUR_FAILURE_URL>", // optional failure url
      flowControl: { ... }          // optional flow control
    })

    console.log(workflowRunId)
    // prints wfr_my-workflow
    ```

    ```ts Multiple Workflows
    import { Client } from "@upstash/workflow";

    const client = new Client({ token: "<QSTASH_TOKEN>" })
    const results = await client.trigger([
      {
        url: "<YOUR_WORKFLOW_ENDPOINT>",
        // other options...
      },
      {
        url: "<YOUR_WORKFLOW_ENDPOINT>",
        // other options...
      },
    ])

    console.log(results[0].workflowRunId)
    // prints wfr_my-workflow
    ```
</CodeGroup>

### 2. Sending an HTTP Request

This approach is recommended for quick testing via curl during development.

You should **NOT** start the workflow run in production by direct calls to your endpoint.

```bash
curl -X POST https://<YOUR_WORKFLOW_ENDPOINT>/<YOUR-WORKFLOW-ROUTE> \
   -H "my-header: foo" \
   -d '{"foo": "bar"}'
```

<Warning>
    If you’ve secured your endpoint with signing keys, only the `trigger` method will work. Direct calls to the endpoint (e.g., via `curl` or `fetch`) will not be possible since `Upstash-Signature` header is missing.

    For more information, read [Secure a workflow](/docs/workflow/howto/security) documentation.
</Warning>

# Webhooks
Source: https://upstash.com/docs/workflow/howto/use-webhooks

This guide explains how to handle webhooks effectively in your Upstash Workflow applications. We'll walk through:

* setting up webhook endpoints
* verifying webhook requests
* and processing webhook events

<Card
  title="GitHub Repository"
  icon="github"
  href="https://github.com/upstash/workflow-js/tree/main/examples/nextjs-webhook-stripe"
  horizontal
>
  You can find the project source code on GitHub.
</Card>

## Overview

Webhooks allow external services to notify your application when events occur. For example, you can use webhooks to receive notifications when a new order is placed in your e-commerce store, a new user signs up, or a new message is sent in your chat application.

Upstash Workflow provides a simple way to receive these events and trigger workflows based on the incoming data autonomously.

### Setting Up Webhook Endpoints

#### Basic Setup

To create a webhook endpoint, use the `serve` function from `@upstash/workflow`:

<CodeGroup>

```typescript TypeScript
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve(
  async (context) => {
    // Your webhook handling logic here
  },
  {
    initialPayloadParser: (payload) => {
      return payload;
    },
  }
);
```

```python Python
from fastapi import FastAPI
from upstash_workflow.fastapi import Serve
from upstash_workflow import AsyncWorkflowContext

app = FastAPI()
serve = Serve(app)

def initial_payload_parser(payload):
    return payload

@serve.post("/api/example", initial_payload_parser=initial_payload_parser)
async def example(context: AsyncWorkflowContext[str]) -> None:
    # Your webhook handling logic here

```

</CodeGroup>

#### Request Validation

Always validate incoming webhook requests to ensure they're legitimate. This way, no one other than the original webhook source can trigger your workflow. Here's an example using Clerk webhooks with Svix:

<CodeGroup>
```typescript Validate and Parse in Workflow - TypeScript
export const { POST } = serve<string>(async (context) => {
	const payloadString = context.requestPayload;
	const headerPayload = context.headers;

    let event: WebhookEvent;
    try {
    	event = await validateRequest(payloadString, headerPayload);
    } catch {
    	return
    }

    // Next steps based on the event

})

```

```python Validate and Parse in Workflow - Python
async def validate_request(payload_string: str, header_payload: dict):
    # Validate the request
    pass

@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:
    payload_string = context.request_payload
    header_payload = context.headers

    try:
        event = await validate_request(payload_string, header_payload)
    except:
        return

    # Next steps based on the event

```

```typescript Validation Function - TypeScript
import { Webhook } from "svix";
import { WebhookEvent } from "@clerk/nextjs/server";

const webhookSecret = "YOUR_WEBHOOK_SECRET";

async function validateRequest(payloadString: string, headerPayload: Headers) {
  const svixHeaders = {
    "svix-id": headerPayload.get("svix-id") as string,
    "svix-timestamp": headerPayload.get("svix-timestamp") as string,
    "svix-signature": headerPayload.get("svix-signature") as string,
  };
  const wh = new Webhook(webhookSecret);
  return wh.verify(payloadString, svixHeaders) as WebhookEvent;
}
```

</CodeGroup>

### Handling Webhook events

Use the context.run method to process webhook events in discrete, trackable steps:

<CodeGroup>

```typescript TypeScript
export const { POST } = serve(async (context) => {
  // ... Parse and validate the incoming request

  const user = await context.run<false | UserPayload>(
    "handle-webhook-event",
    async () => {
      if (event.type === "user.created") {
        const { id: clerkUserId, email_addresses, first_name } = event.data;
        const primaryEmail = email_addresses.find(
          (email) => (email.id = event.data.primary_email_address_id)
        );

        if (!primaryEmail) {
          return false;
        }

        return {
          event: event.type,
          userId: clerkUserId,
          email: primaryEmail.email_address,
          firstName: first_name,
        } as UserPayload;
      }
      return false;
    }
  );
});
```

```python Python
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:
    # ... Parse and validate the incoming request

    async def _handle_webhook_event():
        if event.type == "user.created":
            clerk_user_id = event.data["id"]
            email_addresses = event.data["email_addresses"]
            first_name = event.data["first_name"]

            primary_email = next(
                (
                    email
                    for email in email_addresses
                    if email.id == event.data["primary_email_address_id"]
                ),
                None,
            )

            if not primary_email:
                return False

            return {
                "event": event.type,
                "user_id": clerk_user_id,
                "email": primary_email["email_address"],
                "first_name": first_name,
            }

        return False

    user = await context.run("handle-webhook-event", _handle_webhook_event)

```

</CodeGroup>

After validating the webhook and extracting the initial user data, you'll often need to perform additional operations like creating customer records, sending welcome emails etc.

<CodeGroup>

```typescript TypeScript
export const { POST } = serve(async (context) => {
  // ... Previous validation and user data extraction

  if (!user) {
    return;
  }

  const customer = await context.run("create-stripe-customer", async () => {
    return await stripe.customers.create({
      email: user.email,
      name: `${user.firstName} ${user.lastName}`,
      metadata: {
        userId: user.userId,
      },
    });
  });

  /// ... Additional steps
});
```

```python Python
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:
    # ... Previous validation and user data extraction

    if not user:
        return

    async def _create_stripe_customer():
        return await stripe.customers.create(
            email=user["email"],
            name=f"{user['first_name']} {user['last_name']}",
            metadata={"user_id": user["user_id"]},
        )

    customer = await context.run("create-stripe-customer", _create_stripe_customer)

    # ... Additional steps

```

</CodeGroup>

You're now ready to perform any operation in the following steps.

# Vercel AI SDK
Source: https://upstash.com/docs/workflow/integrations/aisdk

<Note>
  This feature is not yet available in
  [workflow-py](https://github.com/upstash/workflow-py). See our
  [Roadmap](/docs/workflow/roadmap) for feature parity plans and
  [Changelog](/docs/workflow/changelog) for updates.
</Note>

<Card title="GitHub Repository" icon="github" href="https://github.com/upstash/workflow-js/blob/main/examples/nextjs/app/vercel-ai-sdk/route.ts" horizontal>
  You can find the project source code which uses real APIs on Github.
</Card>

Upstash Workflow integrates with the Vercel AI SDK to provide durable and reliable AI applications. This allows you to:

* Build resilient AI applications with automatic retries
* Manage AI operations with workflow steps
* Implement tools and function calling with durability
* Handle errors gracefully across your AI operations
* Handle long-running AI operations with extended timeouts

This guide will walk you through setting up and implementing AI features using Upstash Workflow's durability guarantees with Vercel AI SDK's capabilities.

## Prerequisites

Before getting started, make sure you have:

* An OpenAI API key
* Basic familiarity with Upstash Workflow and Vercel AI SDK
* Vercel AI SDK version 4.0.12 or higher (required for ToolExecutionError handling)

## Installation

Install the required packages:

<CodeGroup>
```bash npm
npm install @ai-sdk/openai ai zod
```

```bash pnpm
pnpm install @ai-sdk/openai ai zod
```

```bash bun
bun install @ai-sdk/openai ai zod
```

</CodeGroup>

## Implementation

### Creating OpenAI client

AI SDKs (Vercel AI SDK, OpenAI SDK etc.) uses the client's default fetch implementation to make API requests, but allows you to provide a custom fetch implementation.

In the case of Upstash Workflow, we need to use the `context.call` method to make HTTP requests. We can create a custom fetch implementation that uses `context.call` to make requests. By using `context.call`, Upstash Workflow is the one making the HTTP request and waiting for the response, even if it takes too long to receive response from the LLM.

The following code snippet can also be generalized to work with other LLM SDKs, such as Anthropic or Google.

```typescript {18-24}
import { createOpenAI } from '@ai-sdk/openai';
import { HTTPMethods } from '@upstash/qstash';
import { WorkflowAbort, WorkflowContext } from '@upstash/workflow';

export const createWorkflowOpenAI = (context: WorkflowContext) => {
  return createOpenAI({
    compatibility: "strict",
    fetch: async (input, init) => {
      try {
        // Prepare headers from init.headers
        const headers = init?.headers
          ? Object.fromEntries(new Headers(init.headers).entries())
          : {};

        // Prepare body from init.body
        const body = init?.body ? JSON.parse(init.body as string) : undefined;

        // Make network call
        const responseInfo = await context.call("openai-call-step", {
          url: input.toString(),
          method: init?.method as HTTPMethods,
          headers,
          body,
        });

        // Construct headers for the response
        const responseHeaders = new Headers(
          Object.entries(responseInfo.header).reduce((acc, [key, values]) => {
            acc[key] = values.join(", ");
            return acc;
          }, {} as Record<string, string>)
        );

        // Return the constructed response
        return new Response(JSON.stringify(responseInfo.body), {
          status: responseInfo.status,
          headers: responseHeaders,
        });
      } catch (error) {
        if (error instanceof WorkflowAbort) {
          throw error
        } else {
          console.error("Error in fetch implementation:", error);
          throw error; // Rethrow error for further handling
        }
      }
    },
  });
};
```

### Using OpenAI client to generate text

Now that we've created the OpenAI client, we can use it to generate the text.

For that, we're going to create a new workflow endpoint that uses the payload as prompt to generate text using the OpenAI client.

```typescript {8, 16-20}
import { serve } from "@upstash/workflow/nextjs";
import { WorkflowAbort } from '@upstash/workflow';
import { generateText, ToolExecutionError } from 'ai';

import { createWorkflowOpenAI } from './model';

export const { POST } = serve<{ prompt: string }>(async (context) => {
  const openai = createWorkflowOpenAI(context);

  // Important: Must have a step before generateText
  const prompt = await context.run("get prompt", async () => {
    return context.requestPayload.prompt;
  });

  try {
    const result = await generateText({
      model: openai('gpt-3.5-turbo'),
      maxTokens: 2048,
      prompt,
    });

    await context.run("text", () => {
      console.log(`TEXT: ${result.text}`);
      return result.text;
    });

  } catch (error) {
    if (error instanceof ToolExecutionError && error.cause instanceof WorkflowAbort) {
      throw error.cause;
    } else {
      throw error;
    }
  }
});
```

We can either [run the app locally](/docs/workflow/howto/local-development/development-server) or deploy it. Once the app is running, we can trigger the workflow using the following code:

```ts
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const { workflowRunId } = await client.trigger({
  url: "https://<YOUR_WORKFLOW_ENDPOINT>/<YOUR-WORKFLOW-ROUTE>",
  body: { "prompt": "How is the weather in San Francisco around this time?" }
});
```

The workflow will execute, and we can view the logs in `the Workflow dashboard`

  <img alt="Workflow logs in dashboard" />

### Advanced Implementation with Tools

Tools allow the AI model to perform specific actions during text generation. You can learn more about tools in the [Vercel AI SDK documentation](https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling).

When using tools with Upstash Workflow, each tool execution must be wrapped in a workflow step.

<Info>
The `maxSteps` parameter must be greater than 1 when using tools to allow the model to process tool results and generate final responses. See the [tool steps documentation](https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling#tool-steps) for detailed explanation.
</Info>

```typescript {24-30, 33}
import { z } from 'zod';
import { serve } from "@upstash/workflow/nextjs";
import { WorkflowAbort } from '@upstash/workflow';
import { generateText, ToolExecutionError, tool } from 'ai';

import { createWorkflowOpenAI } from './model';

export const { POST } = serve<{ prompt: string }>(async (context) => {
  const openai = createWorkflowOpenAI(context);

  const prompt = await context.run("get prompt", async () => {
    return context.requestPayload.prompt;
  });

  try {
    const result = await generateText({
      model: openai('gpt-3.5-turbo'),
      tools: {
        weather: tool({
          description: 'Get the weather in a location',
          parameters: z.object({
            location: z.string().describe('The location to get the weather for'),
          }),
          execute: ({ location }) => context.run("weather tool", () => {
	        // Mock data, replace with actual weather API call
            return {
              location,
              temperature: 72 + Math.floor(Math.random() * 21) - 10,
            };
          })
        }),
      },
      maxSteps: 2,
      prompt,
    });

    await context.run("text", () => {
      console.log(`TEXT: ${result.text}`);
      return result.text;
    });
  } catch (error) {
    if (error instanceof ToolExecutionError && error.cause instanceof WorkflowAbort) {
      throw error.cause;
    } else {
      throw error;
    }
  }
});
```

When called with the same prompt as above, we will see the following logs:

  <img />

## Important Considerations

When using Upstash Workflow with the Vercel AI SDK, there are several critical requirements that must be followed:

### Step Execution Order

The most critical requirement is that `generateText` cannot be called before any workflow step. Always have a step before `generateText`. This could be a step which gets the prompt:

<CodeGroup>
```typescript ❌ Wrong {4}
export const { POST } = serve<{ prompt: string }>(async (context) => {
  const openai = createWorkflowOpenAI(context);

  // Will throw "prompt is undefined"
  const result = await generateText({
    model: openai('gpt-3.5-turbo'),
    prompt: context.requestPayload.prompt
  });
});
```

```typescript ✅ Correct {3-7}
export const { POST } = serve<{ prompt: string }>(async (context) => {
  const openai = createWorkflowOpenAI(context);

  // Get prompt in a step first
  const prompt = await context.run("get prompt", async () => {
    return context.requestPayload.prompt;
  });

  const result = await generateText({
    model: openai('gpt-3.5-turbo'),
    prompt
  });
});
```
</CodeGroup>

### Error Handling Pattern

You must use the following error handling pattern exactly as shown. The conditions and their handling should not be modified:

```typescript {3-9}
try {
  // Your generation code
} catch (error) {
  if (error instanceof ToolExecutionError && error.cause instanceof WorkflowAbort) {
    throw error.cause;
  } else {
    throw error;
  }
}
```

### Tool Implementation

When implementing tools:
* Each tool's `execute` function must be wrapped in a `context.run()` call
* Tool steps should have descriptive names for tracking
* Tools must follow the same error handling pattern as above

Example:
```typescript
execute: ({ location }) => context.run("weather tool", () => {
  // Mock data, replace with actual weather API call
  return {
    location,
    temperature: 72 + Math.floor(Math.random() * 21) - 10,
  };
})
```

# Anthropic
Source: https://upstash.com/docs/workflow/integrations/anthropic

The standard way to call a third-party endpoint in your workflow is by using [`context.call`](/docs/workflow/basics/context#context-call).

However, if you need to call the Anthropic endpoint for text generation ([`/v1/messages`](https://docs.anthropic.com/en/api/messages)), you can leverage the type-safe method `context.api.anthropic.call` method:

<Note>
  `context.api.anthropic.call` is not yet available in
  [workflow-py](https://github.com/upstash/workflow-py). You can use `context.call` instead to work with Anthropic. See our
  [Roadmap](/docs/workflow/roadmap) for feature parity plans and
  [Changelog](/docs/workflow/changelog) for updates.
</Note>

```ts
const { status, body } = await context.api.anthropic.call(
  "Call Anthropic",
  {
    token: "<ANTHROPIC_API_KEY>",
    operation: "messages.create",
    body: {
      model: "claude-3-5-sonnet-20241022",
      max_tokens: 1024,
      messages: [
          {"role": "user", "content": "Hello, world"}
      ]
    },
  }
);

// get text:
console.log(body.content[0].text)
```

The SDK provides predefined types for the body field in both the request parameters and the response, simplifying common use cases. If you need to customize these types, you can override them as shown below:

```ts
type ResponseBodyType = { ... }; // Define your response body type
type RequestBodyType = { ... };  // Define your request body type

const { status, body } = await context.api.anthropic.call<
  ResponseBodyType,
  RequestBodyType
>(
  "Call Anthropic",
  {
    ...
  }
);
```

# Datadog - Upstash QStash Integration
Source: https://upstash.com/docs/workflow/integrations/datadog

This guide walks you through connecting your Datadog account with Upstash QStash for monitoring and analytics of your message delivery, retries, DLQ, and schedules.

<Check >
**Integration Scope**

Upstash Datadog Integration covers Prod Pack.

</Check>

## **Step 1: Log in to Your Datadog Account**

1. Go to [Datadog](https://www.datadoghq.com/) and sign in.

## **Step 2: Install Upstash Application**

1. In Datadog, open the Integrations page.
2. Search for "Upstash" and open the integration.

![integration-tab.png]()

Click "Install" to add Upstash to your Datadog account.

![installation.png]()

## **Step 3: Connect Accounts**

After installing Upstash, click "Connect Accounts". Datadog will redirect you to Upstash to complete account linking.

![connect-acc.png]()

## **Step 4: Select Account to Integrate**

1. On Upstash, select the Datadog account to integrate.
2. Personal and team accounts are supported.

**Caveats**

* The integration can be established once at a time. To change the account scope (e.g., add/remove teams), re-establish the integration from scratch.

![personal.png]()

![team.png]()

## **Step 5: Wait for Metrics Availability**

Once the integration is completed, metrics from QStash (publish counts, success/error rates, retries, DLQ, schedule executions) will start appearing in Datadog dashboards shortly.

![upstash-dashboard.png]()

## **Step 6: Datadog Integration Removal Process**

From Datadog → Integrations → Upstash, press "Remove" to break the connection.

### Confirm Removal

Upstash will stop publishing metrics after removal. Ensure any Datadog API keys/configurations for this integration are also removed on the Datadog side.

## **Conclusion**

You’ve connected Datadog with Upstash QStash. Explore Datadog dashboards to monitor message delivery performance and reliability.

If you need help, contact support.

# OpenAI
Source: https://upstash.com/docs/workflow/integrations/openai

### Calling OpenAI

The standard way to call a third-party endpoint in your workflow is by using [`context.call`](/docs/workflow/basics/context#context-call).

However, if you need to call the OpenAI endpoint for text generation ([`/v1/chat/completions`](https://platform.openai.com/docs/api-reference/chat)), you can leverage the type-safe method `context.api.openai.call` method:

<Note>
  `context.api.openai.call` is not yet available in
  [workflow-py](https://github.com/upstash/workflow-py). You can use `context.call` instead to work with OpenAI. See our
  [Roadmap](/docs/workflow/roadmap) for feature parity plans and
  [Changelog](/docs/workflow/changelog) for updates.
</Note>

```typescript OpenAI
const { status, body } = await context.api.openai.call(
  "Call OpenAI",
  {
    token: "<OPENAI_API_KEY>",
    operation: "chat.completions.create",
    body: {
      model: "gpt-4o",
      messages: [
        {
          role: "system",
          content: "Assistant says 'hello!'",
        },
        {
          role: "user",
          content: "User shouts back 'hi!'"
        }
      ],
    },
  }
);

// get text:
console.log(body.content[0].text)
```

The SDK provides predefined types for the body field in both the request parameters and the response, simplifying common use cases. If you need to customize these types, you can override them as shown below:

```ts
type ResponseBodyType = { ... }; // Define your response body type
type RequestBodyType = { ... };  // Define your request body type

const { status, body } = await context.api.openai.call<
  ResponseBodyType,
  RequestBodyType
>(
  "Call OpenAI",
  {
    ...
  }
);
```

### OpenAI Compatible Provider

If you want to call an OpenAI compatible provider, you can do so using the `baseURL` parameter:

```ts
const { status, body } = await context.api.openai.call(
  "Call Deepseek",
  {
    baseURL: "https://api.deepseek.com",
    token: process.env.DEEPSEEK_API_KEY,
    operation: "chat.completions.create",
    body: {
      model: "deepseek-chat",
      messages: [
        {
          role: "system",
          content: "Assistant says 'hello!'",
        },
        {
          role: "user",
          content: "User shouts back 'hi!'"
        }
      ],
    },
  }
);
```

# Prometheus - Upstash QStash Integration
Source: https://upstash.com/docs/workflow/integrations/prometheus

To monitor your QStash metrics in Prometheus and visualize in Grafana, follow these steps:

<Check >
**Integration Scope**

Upstash Prometheus Integration covers Prod Pack.

</Check>

## **Step 1: Enable Prometheus in Upstash Console**

1. Open the Upstash Console and navigate to QStash.
2. Go to Settings → Monitoring.
3. Enable Prometheus to allow scraping QStash metrics.

![configuration.png]()

## **Step 2: Copy Monitoring Token**

1. After enabling, a monitoring token is generated and displayed.
2. Copy the token. It will be used to authenticate Prometheus requests.

<Check >
**Header Format**

Send the token as `Authorization: Bearer <MONITORING_TOKEN>`.

</Check>

![monitoring-token.png]()

## **Step 3: Configure Prometheus (via Grafana Data Source)**

1. In Grafana, add a Prometheus data source.
2. Set the address to `https://api.upstash.com/monitoring/prometheus`.
3. In HTTP headers, add the monitoring token.

![datasource.png]()

![headers.png]()

Click <b>Test and Save</b>.

![datasource-final.png]()

## **Step 4: Import Dashboard**

You can use the Upstash Grafana dashboard to visualize QStash metrics.

Open the import dialog and use: <a href="https://grafana.com/grafana/dashboards/24206-upstash-qstash-dashboard/">Upstash QStash Dashboard</a>

![grafana-dashboard.png]()

## **Conclusion**

You’ve integrated QStash with Prometheus. Use Grafana to explore message throughput, retries, DLQ, schedules, and Upstash Workflows.

If you encounter issues, contact support.

# Resend
Source: https://upstash.com/docs/workflow/integrations/resend

The standard way to call a third-party endpoint in your workflow is by using [`context.call`](/docs/workflow/basics/context#context-call).

However, if you need to call the Resend endpoint to send emails ([`/emails`](https://resend.com/docs/api-reference/emails/send-email) or [`/emails/batch`](https://resend.com/docs/api-reference/emails/send-batch-emails)), you can leverage the type-safe method `context.api.resend.call` method:

<Note>
  `context.api.resend.call` is not yet available in
  [workflow-py](https://github.com/upstash/workflow-py). You can use `context.call` instead to work with Resend. See our
  [Roadmap](/docs/workflow/roadmap) for feature parity plans and
  [Changelog](/docs/workflow/changelog) for updates.
</Note>

<CodeGroup>

```typescript Single Email
const { status, body } = await context.api.resend.call(
  "Call Resend",
  {
    token: "<RESEND_API_KEY>",
    body: {
      from: "Acme <onboarding@resend.dev>",
      to: ["delivered@resend.dev"],
      subject: "Hello World",
      html: "<p>It works!</p>",
    },
    headers: {
      "content-type": "application/json",
    },
  }
);
```

```typescript Batch Email {4}
const { status, body } = await context.api.resend.call(
  "Call Resend",
  {
    batch: true,
    token: "<RESEND_API_KEY>",
    body: [
      {
        from: "Acme <onboarding@resend.dev>",
        to: ["delivered@resend.dev"],
        subject: "Hello World",
        html: "<p>It works!</p>",
      },
      {
        from: "Acme <onboarding@resend.dev>",
        to: ["delivered@resend.dev"],
        subject: "Hello World",
        html: "<p>It works!</p>",
      },
    ],
    headers: {
      "content-type": "application/json",
    },
  }
);
```

</CodeGroup>

The SDK provides predefined types for the body field in both the request parameters and the response, simplifying common use cases. If you need to customize these types, you can override them as shown below:

```ts
type IsBatch = true; // Set to either true or false
type ResponseBodyType = { ... }; // Define your response body type
type RequestBodyType = { ... };  // Define your request body type

const { status, body } = await context.api.resend.call<
  IsBatch,
  ResponseBodyType,
  RequestBodyType
>(
  "Call Resend",
  {
    ...
  }
);
```

# llms.txt
Source: https://upstash.com/docs/workflow/llms-txt

# Pricing
Source: https://upstash.com/docs/workflow/pricing

Upstash Workflow is based on QStash and uses a "pay-as-you-go" pricing model. You only incur costs when your app receives traffic, meaning there's no charge when it's not in use. Click [here](https://upstash.com/pricing/workflow) to view the pricing.

A workflow run consists of several QStash messages, with the total cost determined by the number of messages used.

You can track your current message usage and associated costs in the [Overview tab of the console](https://console.upstash.com/qstash?tab=details).

  <img />

For detailed pricing information based on different plans, visit our [Workflow pricing page](https://upstash.com/pricing/workflow).

### Message Usage per Workflow Run

* [context.run](/docs/workflow/basics/context#context-run), [context.sleep](/docs/workflow/basics/context#context-sleep), [context.sleepUntil](/docs/workflow/basics/context#context-sleepuntil), or [context.waitForEvent](/docs/workflow/basics/context#context-waitforevent) commands generate a single message.
* The [context.call](/docs/workflow/basics/context#context-call) command generates two messages.
* Each step in a [parallel run](/docs/workflow/howto/parallel-runs) costs 1 extra message.
* If the workflow endpoint or URL in [context.call](/docs/workflow/basics/context#context-call) returns an error or is unreachable, the workflow SDK will retry the call (up to 3 times by default). Each retry counts as a new message.

- [Astro](https://upstash.com/docs/workflow/quickstarts/astro.md)
- [Cloudflare Workers](https://upstash.com/docs/workflow/quickstarts/cloudflare-workers.md)
- [Express.js](https://upstash.com/docs/workflow/quickstarts/express.md)
- [FastAPI](https://upstash.com/docs/workflow/quickstarts/fastapi.md)
- [Flask](https://upstash.com/docs/workflow/quickstarts/flask.md)
- [Hono](https://upstash.com/docs/workflow/quickstarts/hono.md)
- [Next.js & FastAPI](https://upstash.com/docs/workflow/quickstarts/nextjs-fastapi.md)
- [Next.js & Flask](https://upstash.com/docs/workflow/quickstarts/nextjs-flask.md)
- [Nuxt](https://upstash.com/docs/workflow/quickstarts/nuxt.md)
- [Supported Platforms](https://upstash.com/docs/workflow/quickstarts/platforms.md)
- [SolidJS](https://upstash.com/docs/workflow/quickstarts/solidjs.md)
- [SvelteKit](https://upstash.com/docs/workflow/quickstarts/svelte.md)
- [TanStack Start](https://upstash.com/docs/workflow/quickstarts/tanstack-start.md)
- [Next.js](https://upstash.com/docs/workflow/quickstarts/vercel-nextjs.md)

# Roadmap
Source: https://upstash.com/docs/workflow/roadmap

<Note>
  We have moved the roadmap and the changelog to [Github Discussions](https://github.com/orgs/upstash/discussions) starting from October 2025.Now you can follow `In Progress` features. You can see that your `Feature Requests` are recorded. You can vote for them and comment your specific use-cases to shape the feature to your needs.
</Note>

# JavaScript SDK
Source: https://upstash.com/docs/workflow/sdk/workflow-js

# Python SDK
Source: https://upstash.com/docs/workflow/sdk/workflow-py

# Overview
Source: https://upstash.com/docs/workflow/steps

A workflow's **context** is an object provided by the route function.

The context object provides:
* **Workflow APIs** – functions for defining workflow steps.
* **Workflow Run Properties** – request payload, request headers, and other metadata.

<CodeGroup>
    ```typescript api/workflow/route.ts highlight={4-5}
    import { serve } from "@upstash/workflow/nextjs";

    export const { POST } = serve(
      // 👇 the workflow context
      async (context) => {
        // ...
      }
    );
    ```

    ```python main.py
    from fastapi import FastAPI
    from upstash_workflow.fastapi import Serve
    from upstash_workflow import AsyncWorkflowContext

    app = FastAPI()
    serve = Serve(app)

    @serve.post("/api/example")
    async def example(context: AsyncWorkflowContext[str]) -> None: ...

    ```
</CodeGroup>

## Context Object Properties

<ParamField path="requestPayload" type="object">
  The request payload passed to the workflow run via `trigger()` call.
</ParamField>

<ParamField path="headers" type="object">
  The request headers passed to the workflow run via `trigger()` call.
</ParamField>

<ParamField path="workflowRunId" type="string">
  The unique identifier of the current workflow run.
</ParamField>

<ParamField path="url" type="string">
  The public URL of the workflow endpoint.
</ParamField>

<ParamField path="failureUrl" type="string">
  The URL used for workflow failure callback.

  If a failure function is defined, this is the same as the workflow's `url`.
</ParamField>

<ParamField path="env" type="object">
  The environment variables available to the workflow.
</ParamField>

<ParamField path="qstashClient" type="object">
  The QStash client instance used by the workflow endpoint.
</ParamField>

<ParamField path="labels" type="string[]">
  The labels attached to the current workflow run, if set in [client.trigger](/docs/workflow/basics/client/trigger).
  Defaults to an empty array when no label was set.
</ParamField>

<ParamField path="label" type="string | undefined" deprecated>
  Deprecated. Use `labels` instead. When a run has multiple labels, this only
  returns the first one.
</ParamField>

## Context Object Functions

You can use the functions exposed by context object to define workflow steps.

* [context.run](/docs/workflow/basics/context/run)
* [context.sleep](/docs/workflow/basics/context/sleep)
* [context.sleepUntil](/docs/workflow/basics/context/sleepUntil)
* [context.waitForEvent](/docs/workflow/basics/context/waitForEvent)
* [context.createWebhook](/docs/workflow/basics/context/createWebhook)
* [context.waitForWebhook](/docs/workflow/basics/context/waitForWebhook)
* [context.notify](/docs/workflow/basics/context/notify)
* [context.invoke](/docs/workflow/basics/context/invoke)
* [context.call](/docs/workflow/basics/context/call)
* [context.cancel](/docs/workflow/basics/context/cancel)
* [context.api](/docs/workflow/basics/context/api)

# context.api
Source: https://upstash.com/docs/workflow/steps/api

In addition to `context.call`, you can also make third‑party requests using the `context.api` namespace.

This namespace provides built‑in integrations for **OpenAI**, **Anthropic**, and **Resend**, allowing you to make requests in a **type‑safe** manner.

<CodeGroup>

```typescript OpenAI
const { status, body } = await context.api.openai.call("Call OpenAI", {
  token: "<OPENAI_API_KEY>",
  operation: "chat.completions.create",
  body: {
    model: "gpt-4o",
    messages: [
      {
        role: "system",
        content: "Assistant says 'hello!'",
      },
      { role: "user", content: "User shouts back 'hi!'" },
    ],
  },
});
```

```typescript Anthropic
const { status, body } = await context.api.anthropic.call(
  "Call Anthropic",
  {
    token: "<ANTHROPIC_API_KEY>",
    operation: "messages.create",
    body: {
      model: "claude-3-5-sonnet-20241022",
      max_tokens: 1024,
      messages: [
          {"role": "user", "content": "Hello, world"}
      ]
    },
  }
);
```

```typescript Resend
const { status, body } = await context.api.resend.call("Call Resend", {
  token: "<RESEND_API_KEY>",
  body: {
    from: "Acme <onboarding@resend.dev>",
    to: ["delivered@resend.dev"],
    subject: "Hello World",
    html: "<p>It works!</p>",
  },
  headers: {
    "content-type": "application/json",
  },
});
```

</CodeGroup>

We'll continue adding more integrations over time. If you'd like to see a specific integration, feel free to contribute to the SDK or contact us with your suggestion.

For detailed guides on usage and configuration, see the [Integrations section](/docs/workflow/integrations/openai).

# context.call
Source: https://upstash.com/docs/workflow/steps/call

`context.call()` performs an HTTP request as a workflow step, supporting longer response times up to 12 hours.

The request is executed by **Upstash on your behalf**, so your application does not consume compute resources during the request duration.

If the endpoint responds with a non‑success status code (anything outside `200–299`),
`context.call()` still returns the response and the workflow continues.
This allows you to inspect the response (via the `status` field) and decide how to handle failure cases in your logic.

If you want requests to retry automatically, you can explicitly pass a retry configuration.

## Arguments

<ParamField body="url" type="string">
    The URL of the HTTP endpoint to call.
</ParamField>

<ParamField body="method" type="string">
    TThe HTTP method to use (`GET`, `POST`, `PUT`, etc.). Defaults to `GET`.
</ParamField>

<ParamField body="body" type="string">
    The request body as a string.
</ParamField>

<ParamField body="headers" type="object">
   A map of headers to include in the request.
</ParamField>

<ParamField body="retries" >
    Number of retry attempts if the request fails. Defaults to `0` (no retries).
</ParamField>

<ParamField body="retryDelay" >
    Delay between retries (in milliseconds). By default, uses exponential backoff. You can use mathematical expressions and the special variable `retried` (current retry attempt count starting from 0). Examples: `1000`, `pow(2, retried)`, `max(10, pow(2, retried))`.
</ParamField>

<ParamField body="flowControl" type="object" optional>
    Throttle outbound requests.

    See [Flow Control](/docs/workflow/features/flow-control) for details.

    <Expandable title="properties">
        <ParamField body="key" type="string">
          A logical grouping key that identifies which requests share the same flow control limits.
        </ParamField>

        <ParamField body="rate" type="number">
            The maximum number of allowed requests per second.
        </ParamField>

        <ParamField body="parallelism" type="number">
            The maximum number of concurrent requests allowed.
        </ParamField>

        <ParamField body="period" type="string|number">
            The time window used to enforce the defined rate limit. Default is `1s`.
        </ParamField>
    </Expandable>
</ParamField>

<ParamField body="timeout" type="number" >
    Maximum time (in seconds) to wait for a response.
    If retries are enabled, this timeout applies individually to each attempt.
</ParamField>

<ParamField body="workflow" >
    When using [`serveMany`](/docs/workflow/features/invoke/serveMany#using-serve-manyny), you can call another workflow defined in the same `serveMany` by passing it to this parameter.
</ParamField>

## Response

<ResponseField name="status" type="number">
    The HTTP response status code.
</ResponseField>

<ResponseField name="body" type="string">
    The response body.

    `context.call()` attempts to parse the body as JSON.
    If parsing fails, the raw body string is returned.
</ResponseField>

<ResponseField name="headers" type="dictionary">
    The response headers.
</ResponseField>

<Tip>
    In TypeScript, you can declare the expected result type for strong typing:

    ```typescript
    type ResultType = {
      field1: string,
      field2: number
    };

    const result = await context.call<ResultType>( ... );
    ```
</Tip>

## Usage

<CodeGroup>

```javascript TypeScript
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve<{ topic: string }>(async (context) => {
  const { userId, name } = context.requestPayload;

  const { status,  headers,  body } = await context.call("sync-user-data", {
      url: "https://my-third-party-app", // Endpoint URL
      method: "POST",
      body: JSON.stringify({
        userId,
        name
      }),
      headers: {
        authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
      },
    }
  );
});

```

```python Python
from fastapi import FastAPI
from upstash_workflow.fastapi import Serve
from upstash_workflow import AsyncWorkflowContext

app = FastAPI()
serve = Serve(app)

@dataclass
class Request:
    topic: str

@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[Request]) -> None:
    request: Request = context.request_payload

    result = await context.call(
        "generate-long-essay",
        url="https://api.openai.com/v1/chat/completions",
        method="POST",
        body={
            "model": "gpt-4o",
            "messages": [
                {
                    "role": "system",
                    "content": "You are a helpful assistant writing really long essays that would cause a normal serverless function to timeout.",
                },
                {"role": "user", "content": request["topic"]},
            ],
        },
        headers={
            "authorization": f"Bearer {os.environ['OPENAI_API_KEY']}",
        },
    )

    status, headers, body = result.status, result.headers, result.body

```

</CodeGroup>

<Tip>
    We provide integrations for **OpenAI, Anthropic, and Resend**, allowing you to call their APIs with strongly typed request bodies using `context.call`.
    See [`context.api`](/docs/workflow/basics/context#context-api) for details.
</Tip>

<Info>
  The `context.call()` function can make requests to any public API endpoint. However, it cannot:

  * Make requests to localhost (unless you set up a local tunnel, [here's how](/docs/workflow/howto/local-development/local-tunnel))
  * Make requests to internal Upstash QStash endpoints.
</Info>

# context.cancel
Source: https://upstash.com/docs/workflow/steps/cancel

All of the methods covered so far are used to define workflow steps.

`context.cancel` is different — it allows you to **explicitly cancel the current workflow run**.

```ts
export const { POST } = serve<{ topic: string }>(async (context) => {
  const payload = context.requestPayload

  const result = await context.run("check if canceled", () => { ... });

  if (result.cancel) {
    await context.cancel() // cancel the workflow run
  }
})
```

When a workflow run is canceled:

* It is labeled as **canceled** (not failed).
* The configured `failureFunction` **is not triggered**.
* No entries are sent to the **dead-letter queue (DLQ)**.

# context.createWebhook
Source: https://upstash.com/docs/workflow/steps/createWebhook

`context.createWebhook()` creates a unique webhook that can be called by external services to trigger workflow continuation.

The webhook URL generated can be called multiple times to resume multiple [`context.waitForWebhook`](/docs/workflow/basics/context/waitForWebhook) steps.

## Arguments

<ParamField body="stepName" type="string" required>
    Name of the step.
</ParamField>

## Response

<ResponseField name="webhookUrl" type="string">
    The unique webhook URL that external services should call to resume the workflow.

    Can be called multiple times to resume multiple [`context.waitForWebhook`](/docs/workflow/basics/context/waitForWebhook) steps.
</ResponseField>

<ResponseField name="eventId" type="string">
    The internal event identifier associated with this webhook.
    This is primarily used internally by [`context.waitForWebhook`](/docs/workflow/basics/context/waitForWebhook).
</ResponseField>

## Usage

```typescript highlight={4}
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve(async (context) => {
  const webhook = await context.createWebhook("create webhook");

  console.log(webhook.webhookUrl); // Use this URL with external services
});
```

For more complete examples and use cases, see [the page on webhooks](/docs/workflow/features/webhooks).

# context.invoke
Source: https://upstash.com/docs/workflow/steps/invoke

`context.invoke()` triggers another workflow run and pauses until the invoked workflow finishes.

The calling workflow resumes once the invoked workflow either **succeeds**, **fails**, or is **canceled**.

<Note>
    Workflows can only invoke other workflows that were served together in the same `serveMany` route.
    For details, see [Invoke other workflows](/docs/workflow/features/invoke).
</Note>

## Arguments

<ParamField body="workflow" type="function" required>
    The workflow definition to invoke.
    Must be a workflow exposed under the same `serveMany`.
</ParamField>

<ParamField body="body" type="any">
    The payload to send to the invoked workflow.
    This value will be set as `context.requestPayload` in the invoked workflow.
</ParamField>

<ParamField body="headers" type="object" optional>
    Optional HTTP headers to forward to the invoked workflow.
    This value will be set as `context.headers` in the invoked workflow.
</ParamField>

<ParamField body="workflowRunId" type="string" optional>
    Override the workflow run ID for the invoked workflow.
    Defaults to a new ID if not specified.
</ParamField>

<ParamField body="retries" type="number" optional>
    Number of retry attempts configuration of the invoked workflow.
    Defaults to `3`. Retries use exponential backoff.
</ParamField>

<ParamField body="retryDelay" type="number|string" optional>
    Delay between retries of the invoked workflow.
</ParamField>

<ParamField body="flowControl" type="object" optional>
    Flow control configuration of the invoked workflow.

    See [Flow Control](/docs/workflow/features/flow-control) for details.

    <Expandable title="properties">
        <ParamField body="key" type="string">
          A logical grouping key that identifies which requests share the same flow control limits.
        </ParamField>

        <ParamField body="rate" type="number">
            The maximum number of allowed requests per second.
        </ParamField>

        <ParamField body="parallelism" type="number">
            The maximum number of concurrent requests allowed.
        </ParamField>

        <ParamField body="period" type="string|number">
            The time window used to enforce the defined rate limit. Default is `1s`.
        </ParamField>
    </Expandable>
</ParamField>

## Response

<ResponseField name="body" type="any">
    The response body returned by the invoked workflow.
</ResponseField>

<ResponseField name="isFailed" type="boolean">
    `true` if the invoked workflow completed with failure.
</ResponseField>

<ResponseField name="isCanceled" type="boolean">
    `true` if the invoked workflow was canceled before completion.
</ResponseField>

## Usage

```ts
const { body, isFailed, isCanceled } = await context.invoke(
  "invoke another workflow",
  {
    workflow: anotherWorkflow,
    body: "test",
    header: {...}, // headers to pass to anotherWorkflow (optional)
    retries,       // number of retries (optional, default: 3)
    retryDelay,    // delay between retries (optional, uses exponential backoff by default)
    flowControl,   // flow control settings (optional)
    workflowRunId  // workflowRunId to set (optional)
  }
);
```

# context.notify
Source: https://upstash.com/docs/workflow/steps/notify

`context.notify()` notifies workflows that are waiting for a specific event, passing along an optional payload.

It is typically used in combination with [`context.waitForEvent`](/docs/workflow/basics/context#context-waitforevent).

## Arguments

<ParamField body="stepName" type="string">
    A unique identifier for the step.
</ParamField>

<ParamField body="eventId" type="string">
    The identifier of the event to notify.
    Must match the `eventId` used in `context.waitForEvent`.
</ParamField>

<ParamField body="eventData" type="any">
    Data to deliver to the waiting workflow(s).
    This value will be returned in `eventData` from the corresponding `waitForEvent` call.
</ParamField>

<ParamField body="workflowRunId" type="string" optional>
    The workflow run ID to notify. When provided, enables **lookback functionality** - the notification will be stored and delivered even if `notify` is called before `waitForEvent`.

    This solves race conditions where notifications might be sent before a workflow reaches its wait step.
</ParamField>

## Response

`context.notify()` returns a list of waiters describing the workflows that were notified.

<ResponseField name="notifyResponse" type="NotifyResponse[]">
    A list of `NotifyResponse` objects describing each workflow that was waiting on the event.

    <Expandable defaultOpen>
        <ResponseField name="messageId" type="string">
            The ID of the notification message delivered to the workflow.
            This is unique to every notification.
        </ResponseField>

        <ResponseField name="workflowRunId" type="string">
            The unique identifier of the workflow run that was notified.
        </ResponseField>

        <ResponseField name="workflowCreatedAt" type="number">
            Unix timestamp (in milliseconds) representing when the workflow was created.
        </ResponseField>

        <ResponseField name="Waiter" type="object">
  <Expandable>
   	<ResponseField name="url" type="string" required>
      URL to call upon notify
    </ResponseField>
   	<ResponseField name="deadline" type="number" required>
      Unix timestamp for when the wait will time out
    </ResponseField>
   	<ResponseField name="headers" type="Record<string, string[]>" required>
      Headers sent in case of notify
    </ResponseField>
   	<ResponseField name="timeoutUrl" type="string">
      URL to call upon timeout
    </ResponseField>
   	<ResponseField name="timeoutBody" type="unknown">
      Body used in timeout request
    </ResponseField>
   	<ResponseField name="timeoutHeaders" type="Record<string, string[]>">
      Headers sent in case of time out
    </ResponseField>
  </Expandable>
</ResponseField>
    </Expandable>

</ResponseField>

## Usage

### Basic Notification

```javascript
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve<{ topic: string }>(async (context) => {
  const payload = context.requestPayload;

  const {
    notifyResponse, // result of notify, which is a list of notified waiters
  } = await context.notify("notify step", "my-event-Id", payload);
});
```

### Notification with Lookback

To prevent race conditions, you can provide a `workflowRunId`. This enables lookback - the notification will be stored and delivered even if sent before the target workflow reaches `waitForEvent`:

```javascript
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve<{ orderId: string }>(async (context) => {
  const { orderId } = context.requestPayload;

  // Process payment
  await context.run("process-payment", async () => {
    return processPayment(orderId);
  });

  // Notify a specific workflow run with lookback support
  const {
    notifyResponse,
  } = await context.notify(
    "notify payment complete",
    "payment-processed",
    { orderId, status: "success" },
    "wfr_order_processor_123" // Enables lookback for this workflow run
  );
});
```

# context.run
Source: https://upstash.com/docs/workflow/steps/run

`context.run()` executes a piece of custom business logic as a workflow step.

It returns a `Promise`, so you can decide how steps execute:
* **Sequentially** by awaiting them one by one.
* **In parallel** by awaiting multiple steps together.

##  Arguments

<ParamField body="stepName" type="string">
    A unique identifier for the step.
</ParamField>

<ParamField body="stepFunction" type="function">
    The business logic to run inside this step.
</ParamField>

## Response

Each step can return a JSON-serializable value—anything from simple primitives to complex objects.

The value is **JSON-serialized** and automatically restored across requests.

<Warning>
    Avoid returning stateful resources such as database connections or file handles.

    Instead, return plain data (numbers, strings, arrays, objects) so the result can be safely persisted and restored across workflow executions.
</Warning>

## Usage

<CodeGroup>
    ```typescript Serial execution (TypeScript) highlight={6-8, 10-12}
    import { serve } from "@upstash/workflow/nextjs";

    export const { POST } = serve<string>(async (context) => {
      const input = context.requestPayload;

      const result1 = await context.run("step-1", async () => {
        return someWork(input);
      });

      await context.run("step-2", async () => {
        someOtherWork(result1);
      });
    });

    ```

    ```typescript Parallel execution (TypeScript)
    import { serve } from "@upstash/workflow/nextjs"

    export const { POST } = serve<string>(
      async (context) => {
        const input = context.requestPayload;

        const promise1 = context.run("step-1", async () => {
          return someWork(input);
        });

        const promise2 = context.run("step-2", async () => {
          return someOtherWork(input);
        });

        await Promise.all([promise1, promise2]);
      },
    );
    ```

    ```python Serial execution (Python)
    from fastapi import FastAPI
    from upstash_workflow.fastapi import Serve
    from upstash_workflow import AsyncWorkflowContext

    app = FastAPI()
    serve = Serve(app)

    @serve.post("/api/example")
    async def example(context: AsyncWorkflowContext[str]) -> None:
        input = context.request_payload

        async def _step1():
            return some_work(input)

        result1 = await context.run("step-1", _step1)

        async def _step2():
            return some_other_work(result1)

        await context.run("step-2", _step2)

    ```
</CodeGroup>

<Info>
    Because results are JSON-serialized, **class instances are restored as plain objects**.
    This means instance methods will not be available unless you explicitly rehydrate the object.

    To fix this, you can recreate the instance using Object.assign() or a custom factory:
    ```typescript
   export const { POST } = serve(
    async (context) => {

       let user = await context.run("step-1", async () => {
         // 👇 Return a class instance from step
         return new User("John Doe", "john.doe@example.com");
       });

       // 👇 Properties are accessible by default
       console.log(user.name)

       // 👇 Create a Proper Instance with Object.assign()
       user = Object.assign(new User(), user);

       await context.run("greet", async () => {
         // 👇 Now instance methods are available as well
         console.log(user.greet());
       });
     }
   );
    ```
</Info>

# context.sleep
Source: https://upstash.com/docs/workflow/steps/sleep

`context.sleep()` pauses workflow execution for a specified duration.

When a workflow is paused, the current request completes and a new one is automatically scheduled to resume after the delay.
This ensures no compute resources are consumed during the sleep period.

<Note>Always `await` a `sleep` step to properly pause execution.</Note>

##  Arguments

<ParamField body="stepName" type="string">
    A unique identifier for the step.
</ParamField>

<ParamField body="duration" type="number|string">
    The duration to pause workflow execution.

    * **Human-readable string format:**

    | Input   | Duration    |
    |---------|-------------|
    | `"10s"` | 10 seconds  |
    | `"1m"`  | 1 minute    |
    | `"30m"` | 30 minutes  |
    | `"2h"`  | 2 hours     |
    | `"1d"`  | 1 day       |
    | `"1w"`  | 1 week      |
    | `"1mo"` | 1 month     |
    | `"1y"`  | 1 year      |

    * **Numeric format (seconds):**

    | Input   | Duration      |
    |---------|---------------|
    | `60`    | 60 seconds (1 minute) |
    | `3600`  | 3600 seconds (1 hour) |
    | `86400` | 86400 seconds (1 day) |
</ParamField>

## Usage

<CodeGroup>

```typescript TypeScript highlight={12-13}
import { serve } from "@upstash/workflow/nextjs";
import { signIn, sendEmail } from "@/utils/onboarding-utils";

export const { POST } = serve<User>(async (context) => {
  const userData = context.requestPayload;

  const user = await context.run("sign-in", async () => {
    const signedInUser = await signIn(userData);
    return signedInUser;
  });

  // 👇 Wait for one day (in seconds)
  await context.sleep("wait-until-welcome-email", "1d");

  await context.run("send-welcome-email", async () => {
    return sendEmail(user.name, user.email);
  });
});
```

```python Python
from fastapi import FastAPI
from upstash_workflow.fastapi import Serve
from upstash_workflow import AsyncWorkflowContext
from onboarding_utils import sign_in, send_email

app = FastAPI()
serve = Serve(app)

@serve.post("/api/onboarding")
async def onboarding(context: AsyncWorkflowContext[User]) -> None:
    user_data = context.request_payload

    async def _sign_in():
        return await sign_in(user_data)

    user = await context.run("sign-in", _sign_in)

    # 👇 Wait for one day (in seconds)
    await context.sleep("wait-until-welcome-email", "1d")

    async def _send_email():
        return await send_email(user.name, user.email)

    await context.run("send-welcome-email", _send_email)

```

</CodeGroup>

# context.sleepUntil
Source: https://upstash.com/docs/workflow/steps/sleepUntil

`context.sleepUntil()` pauses workflow execution until a specific timestamp.

When a workflow is paused, the current request completes and a new one is automatically scheduled to resume at the target time.
This ensures no compute resources are consumed while sleeping.

<Note>Always await a `sleepUntil` step to properly pause execution.</Note>

##  Arguments

<ParamField body="stepName" type="string">
    A unique identifier for the step.
</ParamField>

<ParamField body="datetime" type="Date|number|string">
    The target time when the workflow should resume.
    Accepted formats:
    * A **number**: Unix timestamp in seconds
    * A **Date object**
    * A **string** that can be parsed by `new Date(string)` in JavaScript
</ParamField>

## Usage

<CodeGroup>

```typescript TypeScript highlight={11-16}
import { serve } from "@upstash/workflow/nextjs";
import { signIn, sendEmail } from "@/utils/onboarding-utils";

export const { POST } = serve<User>(async (context) => {
  const userData = context.requestPayload;

  const user = await context.run("sign-in", async () => {
    return signIn(userData);
  });

  // 👇 Calculate the date for one week from now
  const oneWeekFromNow = new Date();
  oneWeekFromNow.setDate(oneWeekFromNow.getDate() + 7);

  // 👇 Sleep until the calculated date
  await context.sleepUntil("wait-for-one-week", oneWeekFromNow);

  await context.run("send-welcome-email", async () => {
    return sendEmail(user.name, user.email);
  });
});
```

```python Python
from fastapi import FastAPI
from datetime import datetime, timedelta
from upstash_workflow.fastapi import Serve
from upstash_workflow import AsyncWorkflowContext
from onboarding_utils import sign_in, send_email

app = FastAPI()
serve = Serve(app)

@serve.post("/api/onboarding")
async def onboarding(context: AsyncWorkflowContext[User]) -> None:
    user_data = context.request_payload

    async def _sign_in():
        return await sign_in(user_data)

    user = await context.run("sign-in", _sign_in)

    # 👇 Calculate the date for one week from now
    one_week_from_now = datetime.now() + timedelta(days=7)

    # 👇 Wait until the calculated date
    await context.sleep_until("wait-for-one-week", one_week_from_now)

    async def _send_email():
        return await send_email(user.name, user.email)

    await context.run("send-welcome-email", _send_email)

```

</CodeGroup>

# context.waitForEvent
Source: https://upstash.com/docs/workflow/steps/waitForEvent

`context.waitForEvent` pauses workflow execution until a given event occurs or a timeout is reached.

Default timeout value is 7 days.

## Arguments

<ParamField body="stepId" type="string" required>
    A unique identifier for the step.
</ParamField>

<ParamField body="eventId" type="string" required>
    A unique identifier for the event to wait on.
</ParamField>

<ParamField body="timeout" type="number|string">
    The maximum time to wait before continuing execution.

    * **String format**: Human‑readable duration (e.g., `"10s"`, `"2h"`, `"1d"`).
    * **Number format**: Duration in seconds (e.g., `60`, `3600`).

    Defaults to `7d` (7 days).
</ParamField>

## Response

<ResponseField name="eventData" type="string|object">
    The data passed in when the event is triggered via `notify()`.
</ResponseField>

<ResponseField name="timeout" type="boolean">
    `true` if execution resumed because the timeout elapsed,
    `false` if resumed due to the event being received.
</ResponseField>

## Usage

```javascript highlight={6-11}
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve<{ topic: string }>(async (context) => {
  const request = context.requestPayload;

  const {
    eventData,
    timeout,
  } = await context.waitForEvent("wait for some event", "my-event-id", {
    timeout: "1000s", // 1000 second timeout
  });
});

```

# context.waitForWebhook
Source: https://upstash.com/docs/workflow/steps/waitForWebhook

`context.waitForWebhook()` pauses workflow execution until the webhook created by `createWebhook` is called or a timeout is reached.

You can call `context.waitForWebhook` with the same `webhook` object multiple times to wait for multiple calls to the same webhook URL.

## Arguments

<ParamField body="stepName" type="string" required>
    Name of the step.
</ParamField>

<ParamField body="webhook" type="object" required>
    The webhook object returned by `context.createWebhook()`.

    <Expandable title="properties">
        <ParamField body="webhookUrl" type="string">
            The webhook URL to wait for.
        </ParamField>

        <ParamField body="eventId" type="string">
            The internal event identifier.
        </ParamField>
    </Expandable>
</ParamField>

<ParamField body="timeout" type="string" required>
    The maximum time to wait before continuing execution.

    Should be passed in Human‑readable duration (e.g., `"10s"`, `"2h"`, `"1d"`).
</ParamField>

## Response

The response varies depending on whether the webhook was called before the timeout:

<ResponseField name="timeout" type="boolean">
    * `false` if the webhook was called successfully
    * `true` if execution resumed because the timeout elapsed
</ResponseField>

<ResponseField name="request" type="Request | undefined">
    The HTTP request object received by the webhook (only present when `timeout` is `false`).

    Contains the full request details including method, headers, body, and URL.
</ResponseField>

## Usage

### Basic Example

```typescript highlight={8-13}
import { serve } from "@upstash/workflow/nextjs";

export const { POST } = serve(async (context) => {
  // Create webhook
  const webhook = await context.createWebhook("create webhook");

  // Wait for webhook to be called with 30 second timeout
  const webhookResponse = await context.waitForWebhook(
    "wait for webhook",
    webhook,
    "30s"
  );

  if (webhookResponse.timeout) {
    console.log("Webhook was not called within the timeout period");
  } else {
    console.log("Webhook was called successfully");
    console.log("Request body:", webhookResponse.request.body);
    console.log("Request headers:", webhookResponse.request.headers);
  }
});
```

For more complete examples and use cases, see [the page on webhooks](/docs/workflow/features/webhooks).

# General
Source: https://upstash.com/docs/workflow/troubleshooting/general

## Running Steps Inside try/catch Blocks

When running steps (run, sleep, call, or any other step) inside a try/catch block, you'll encounter the following error:

```
WorkflowAbort: This is an Upstash Workflow error thrown after
a step executes. It is expected to be raised. Make sure that
you await for each step. Also, if you are using try/catch
blocks, you should not wrap context.run/sleep/sleepUntil/call
methods with try/catch. Aborting workflow after executing
step 'some-step'.
```

The `WorkflowAbort` error is thrown by the Workflow SDK when a step executes successfully. When you run a step inside a try/catch block, you have two primary options:

1. **Re-throw WorkflowAbort**: If you need a try/catch block, re-throw the `WorkflowAbort` error:

2. **Avoid Running Steps in try/catch**: The simplest solution is to not wrap steps in try/catch blocks. If you have a `context.run` inside try/catch, you can try putting the try/catch inside the context.run.

<CodeGroup>

```typescript TypeScript {1, 6-7}
import { WorkflowAbort } from '@upstash/workflow';

try {
  await context.run( ... );
} catch (error) {
  if (error instanceof WorkflowAbort) {
    throw error;
  } else {
    // handle other errors
  }
}
```

``` python Python {1, 6-7}
from upstash_workflow import WorkflowAbort

try:
    await context.run( ... )
except Exception as e:
    if isinstance(e, WorkflowAbort):
        raise e
    else:
        # handle other errors

```

</CodeGroup>

## `context.requestPayload` Becoming Undefined

### Headers Considerations

In some frameworks, you may need to pass specific headers for the workflow to access `requestPayload`:

* Try passing `Content-type: text/plain` or `Content-type: application/json` headers when starting the workflow.
* Note that `publishJSON` can only publish `Content-type: application/json`.

### `context.call` Execution

[During a workflow run, the endpoint will be called multiple times](/docs/workflow/basics/how). While executing `context.call`, the endpoint is called at least twice, with the SDK attempting to run the route function until the first step for custom authorization.

Accessing `context.requestPayload` before any step can result in it becoming undefined:

<CodeGroup>

```typescript TypeScript {2-4, 8}
export const { POST } = serve(async (context) => {
  // Will print undefined while executing context.call
  const payload = context.requestPayload
  console.log(payload)

  // ... steps or any other code

  context.call( ... )
})
```

``` python Python {3-5, 9}
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:
    # Will print None while executing context.call
    payload = context.request_payload
    print(payload)

    # ... steps or any other code

    context.call( ... )

```

</CodeGroup>

This behavior stems from the internal mechanics of `context.call`, and resolving this specific interaction is on our roadmap. Note that if you are passing `context.requestPayload` as a parameter to `context.call`, the payload remains intact. However, during the actual execution of `context.call`, the `context.requestPayload` becomes undefined due to the SDK's internal workflow processing steps.

To fix this issue, you can try adding a step which returns the payload:

<CodeGroup>

```typescript TypeScript {2-4, 7}
export const { POST } = serve(async (context) => {
  // Payload will never be undefined
  const payload = await context.run("get payload", () => context.requestPayload)

  // ... steps or any other code

  context.call( ... )
})
```

``` python Python {4-8, 12}
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:

    async def _get_payload() -> str:
        return context.request_payload

    # Payload will never be None
    payload = await context.run("get payload", _get_payload)

    # ... steps or any other code

    context.call( ... )

```

</CodeGroup>

You can find an example of this usage in [our documentation on usage with AI SDK](/docs/workflow/integrations/aisdk).

## Verification Failed Scenarios

When [QStash signature verification](/docs/workflow/howto/security#using-qstashs-built-in-request-verification-recommended) is enabled, you might encounter an error like:

```
Failed to verify that the Workflow request comes from QStash: some-error

If signature is missing, trigger the workflow endpoint by publishing your request to QStash instead of calling it directly.

If you want to disable QStash Verification, you should clear env variables QSTASH_CURRENT_SIGNING_KEY and QSTASH_NEXT_SIGNING_KEY
```

Troubleshooting verification errors:

* Ensure you [start the workflow using `client.trigger` or by publishing to QStash](/docs/workflow/howto/start).
* Verify that `QSTASH_CURRENT_SIGNING_KEY` and `QSTASH_NEXT_SIGNING_KEY` environment variables are correct.
* Pass [appropriate `Content-type` headers](/docs/workflow/troubleshooting/general#headers-considerations) when starting the workflow.

## Authorization Error Handling

Consider this workflow:

<CodeGroup>

```typescript TypeScript {2-5}
export const { POST } = serve(async (context) => {

  if (someCondition()) => {
    return;
  }

  // rest of the workflow
})
```

``` python Python {3-4}
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:
    if some_condition():
        return

    # rest of the workflow

```

</CodeGroup>

Returning before running any steps will result in:
* HTTP status: 400
* Error message: `Failed to authenticate Workflow request.`

This behavior is a direct result of the [custom authorization mechanism](/docs/workflow/howto/security#custom-authorization-method). The Workflow SDK interprets an early function return without executing any steps as an authentication failure.

If the function `someCondition()` is non-deterministic, the recommended approach is to transform this condition into an explicit workflow step. Here's the recommended pattern for handling such non-deterministic conditions:

<CodeGroup>

```typescript TypeScript {3-6}
export const { POST } = serve(async (context) => {

  const shouldReturn = await context.run("check condition", () => someCondition())
  if (shouldReturn) => {
    return;
  }

  // rest of the workflow
})
```

``` python Python {4-9}
@serve.post("/api/example")
async def example(context: AsyncWorkflowContext[str]) -> None:

    async def _check_condition() -> bool:
        return some_condition()

    should_return = await context.run("check condition", _check_condition)
    if should_return:
        return

    # rest of the workflow

```

</CodeGroup>

## Retry Configuration

Retry settings can be configured in two locations:

1. [**Workflow Start** (client.trigger)](/docs/workflow/howto/start): For the triggered workflow run
   * Default retries: 3

2. [**Context Call**](/docs/workflow/basics/context#context-call): For third party requests
   * Default retries: 0

## Verbose Mode Diagnostics

<Note>
  This feature is not yet available in
  [workflow-py](https://github.com/upstash/workflow-py). See our
  [Roadmap](/docs/workflow/roadmap) for feature parity plans and
  [Changelog](/docs/workflow/changelog) for updates.
</Note>

Use [verbose mode](/docs/workflow/basics/serve#verbose) to diagnose workflow issues.

The logs in this section should be seen very rarely. If you observe these logs consistently, you can reach out to our support.

### Localhost in URL Warning

```
Workflow URL contains localhost. This can happen in local development,
but shouldn't happen in production unless you have a route which contains
localhost. Received: ...
```

This error indicates that the workflow URL has localhost. Publish requests will fail and workflow won't be able to run.

**Potential Solutions:**
* Verify [baseUrl](/docs/workflow/basics/serve#baseurl)
* Check UPSTASH_WORKFLOW_URL
* Explicitly pass the full [url](/docs/workflow/basics/serve#url)

### Deduplication Log
Since QStash has at least once delivery guarantee, there is a very small chance that a step will run twice. This is why we suggest [idempotancy](/docs/workflow/basics/caveats#ensure-idempotency-in-context-run). When this happens, the duplicate step should print the following log and terminate:

```
Upstash Workflow: The step 'some-step' with id 'step-index' has run twice during workflow execution. Rest of the workflow will continue running as usual.
```

### Network Issue Warnings

In rare instances, the workflow endpoint may complete its task successfully but fail to send a proper response. The SDK is designed to detect such occurrences. When this happens, the workflow endpoint will return a `200` status code and log one of the following three warnings:

```
Workflow run wfr-*** already exists. A new one isn't created.
tried to append to a cancelled workflow. exiting without publishing.
Failed to remove workflow run wfr-*** as it doesn't exist.
```

A similar issue can occur if you have two parallel context.call steps at the end of your workflow. While the workflow will execute successfully, you may see this warnings:

```
Couldn't fetch workflow run steps. This can happen if the workflow run successfully ends before some callback is executed.
```

## HTTPS Protocol Issue

The Workflow SDK automatically infers the protocol (HTTP or HTTPS) based on the incoming request object in your application.

However, if your app is deployed behind a proxy (e.g., Railway or similar platforms), the proxy may terminate SSL and forward the request to your app using plain HTTP.
This can cause the SDK to mistakenly infer that the request is using HTTP instead of HTTPS.

To fix this, explicitly set the `UPSTASH_WORKFLOW_URL` environment variable to the base URL of your application.
This disables automatic protocol inference and uses the provided static URL instead.

If you’re using Express.js behind a front-facing proxy, an alternative solution is to enable [proxy trust](https://expressjs.com/en/api.html#app.set) so Express correctly identifies the original protocol from the X-Forwarded-Proto header:

```javascript
app.set("trust proxy", true)
```

## Workflow Stuck in First Step

Imagine that you trigger your workflow with [client.trigger](/docs/workflow/basics/client/trigger) and the workflow starts (you see the run in the dashboard), but it doesn't proceed beyond the first step.

If it appears like the initial step has failed:
* Expand the step in the dashboard to see detailed logs to check for any error messages or stack traces that can provide insights into what went wrong.
* Ensure that the workflow endpoint is accessible. You can test by sending a `curl` request to see if the request lands on the endpoint.
* Ensure that the URL you passed to the `client.trigger` method matches the workflow endpoint URL.
* Verify that you're using the latest SDK version both in the workflow endpoint definition and in the code that calls `client.trigger`.

If it appears like the initial step has completed but the workflow is still stuck:
* Workflow SDK could be unable to correctly infer the workflow URL due to a proxy or an issue in the request object. To check if this is the issue, try passing the [`baseUrl` parameter to the `serve` method](/docs/workflow/basics/serve/advanced#param-base-url). This will override the automatic URL inference and use the provided base URL instead.

## Non-workflow Destination Error

Full error:

```
detected a non-workflow destination for trigger/invoke.
make sure you are sending the request to the correct endpoint
```

This error occurs when you call a non-workflow endpoint with `client.trigger` or via Request Builder on Upstash Console. If you check Upstash Console, you will see that the workflow run has started but failed before running any steps:

<img />

Another way you can encounter this error is if you are calling a workflow endpoint on an older SDK version (before 0.2.17 in TypeScript and 0.1.4 in Python) from a newer SDK version. If this happens, in the logs, you will see that the first step of the workflow run has completed successfully, but the workflow fails immediately after that with the same error.

To fix this error, ensure that:
* You are calling a valid workflow endpoint.
* Both the caller and the workflow endpoint are using the latest SDK versions.

# Vercel
Source: https://upstash.com/docs/workflow/troubleshooting/vercel

# Common Issues and Solutions

### Preview Deployment Protection

**Problem:**
When Deployment Protection setting is enabled on Vercel, it's not possible to trigger and complete a workflow run on a preview deployment.

**Solution:**
Vercel provides a way to bypass this protection by using a bypass secret. To create one, follow these steps:

<Steps>
	<Step title="Settings">Go to **Deployment Protection** section under your Vercel project settings.</Step>

	<Step title="Find related section">
		Click on **Add Secret** under **Protection Bypass for Automation** section.
  			<img />
	</Step>

	<Step title="Generate a bypass token">Don't forget to save it and store it as an environment variable (e.g., `VERCEL_AUTOMATION_BYPASS_SECRET`).</Step>
</Steps>

Now that you have a bypass token, you need to pass it as a header in two places to bypass deployment protection.

## Using the Bypass Secret

### Step 1: Configure QStash Client with Headers

[Configure the QStash client](/docs/workflow/basics/serve/advanced#param-qstash-client) with the bypass header in your workflow serve options. This ensures that all workflow steps, including `context.invoke` calls, will include the bypass header automatically:

```ts
import { Client } from '@upstash/qstash'
import { serve } from '@upstash/workflow/nextjs'

export const { POST } = serve<string>(async (context) => {
  // your workflow logic
}, {
  qstashClient: new Client({
    token: process.env.QSTASH_TOKEN!,
    headers: {
      "x-vercel-protection-bypass": process.env.VERCEL_AUTOMATION_BYPASS_SECRET!
    }
  })
})
```

### Step 2: Pass Header When Triggering

When [triggering the workflow](/docs/workflow/howto/start) using `curl` or `client.trigger`, you must also pass the bypass secret as a header:

<CodeGroup>
```ts client.trigger
import { Client } from "@upstash/workflow";

const client = new Client({ token: "<QSTASH_TOKEN>" })

await client.trigger({
  url: "https://vercel-preview.com/workflow",
  headers: {
    "x-vercel-protection-bypass": process.env.VERCEL_AUTOMATION_BYPASS_SECRET!
  },
  body: "Hello world!"
})
```

```bash cURL
curl -X POST \
  'https://vercel-preview.com/workflow' \
  -H 'x-vercel-protection-bypass: <PROTECTION_BYPASS_SECRET>' \
  -d 'Hello world!'
```
</CodeGroup>

<Warning>
	Note that you should apply both of the steps above. The header must be passed when triggering the workflow AND configured in the QStash client for subsequent workflow steps to work properly.
</Warning>