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`](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. # 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 ``` # 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 ``` When credentials come from multiple sources, precedence is: `flags` > `environment variables` > `.env` > `saved config file` # Usage ```bash 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. Pipe it 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). Find the GitHub repository [here](https://github.com/upstash/mcp). 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. # Example prompts ## Redis * "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) 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. 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. 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 ``` 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"] } } } ``` 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"] } } } ``` 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 } } } ``` 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 ``` If you see startup timeout errors, increase `startup_timeout_sec` to `40`. 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. [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"] } } } ``` 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"] } } } ``` 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"] } } } ``` 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"] } } } ``` # 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). The `upstash` skill also includes the [`@upstash/cli`](/docs/agent-resources/cli) reference, so one install is all your agent needs. # 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. 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. # 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, env }) => { 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(); } ``` `ttl: 120` means the box auto-deletes after 2 minutes even if the `finally` block is skipped. For longer-running scripts, increase this value. *** ## 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 (

Code Interpreter

{messages.map((message) => (
{message.role === "user" ? "You" : "Assistant"}
{message.parts.map((part, i) => { if (part.type === "text") { return (

{part.text}

); } 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 (
{toolName}{" "} {isDone ? "✓" : "running…"} {isDone && p.output && (
                        {String(p.output.output)}
                      
)}
); } return null; })}
))}
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" />
); } ``` *** ## 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 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 { 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. Hermes is resource-intensive, so we recommend starting with a Medium box to ensure a smooth installation. *** ## 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 @us-east-1.box.upstash.com ``` *** ## 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. 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. # 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. *** ## 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 @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. *** ## 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= ``` Congratulations! You have successfully set up OpenClaw on your Upstash Box. Your AI gateway is now running and accessible through the secure SSH tunnel. *** ## 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. 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 @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. # 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. *** ## 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 @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. *** ## 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://-3000.preview.box.upstash.com ``` # 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. *** ## 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 and extract ...", }) 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 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 ``` ```tsx {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!, }, }) ``` ```bash title=".env" {2} UPSTASH_BOX_API_KEY=box_xxxxxxxxxxxxxxxxxxxxxxxx OPENCODE_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxx ``` ```tsx {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!, }, }) ``` ```bash title=".env" {2} UPSTASH_BOX_API_KEY=box_xxxxxxxxxxxxxxxxxxxxxxxx OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxx ``` ```tsx {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!, }, }) ``` ### Agent Options You can pass provider-specific agent options with the `options` field on `box.agent.run()` and `box.agent.stream()`. ```tsx const run = await box.agent.run({ prompt: "Refactor the auth flow and keep changes minimal", options: { effort: "medium", maxTurns: 12, }, }) ``` 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` To bring your own agent process, use a [custom agent](/docs/box/overall/custom-agent). ## Quickstart ### Codebase refactor Use Claude Code when you want an agent to work through a larger code change with filesystem, shell, and git access. ```tsx 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) ``` ### Fast code review summary Use OpenCode when you want a concise review or summary over an existing diff or repository state. ```tsx 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) ``` ### Structured analysis Use Codex when you want a strongly structured response that you can feed into another system. ```tsx 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) ``` Clone a repository, run the agent, and open a pull request. ```tsx 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", }) ``` Run one box per file, process in parallel, and return ranked structured results. ```tsx 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) ``` *** ## 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. ```tsx const stream = await box.agent.stream({ prompt: "Review the latest git diff and summarize risks", options: { effort: "high", maxTurns: 20, }, }) ``` ### 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 }` 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. ```tsx 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) ``` ### 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: ```tsx 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-...", }, }, }) ``` Any HTTPS request from inside the box to a matching host will automatically include the configured headers. ```tsx const result = await box.exec.command( 'curl -s https://api.stripe.com/v1/charges?limit=1' ) ``` 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 # 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. ```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!, }, }) ``` ## Agent Contract For each run, Box executes your command and appends these arguments: ```bash -p "" --model "" --stream --session "" ``` `--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: ```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, } }) ``` ## 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: ```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) ``` ## Update an Existing Custom Agent You can update the custom agent command for an existing custom box: ```ts await box.configureCustomHarness({ command: "node", args: ["/workspace/home/another-agent.mjs"], protocol: "box-sse-v1", }) ``` 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 ## 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 # 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 # 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 ```typescript 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 }) ``` **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 ```typescript const run = await box.exec.command("echo hello") console.log(run.result) // "hello" ``` ### Execute inline code ```typescript const result = await box.exec.code({ code: 'console.log(JSON.stringify({ sum: 1 + 2 }))', lang: "js", }) ``` ### File operations ```typescript await box.files.write({ path: "data.json", content: '{"key":"value"}' }) const content = await box.files.read("data.json") ``` ### Clean up early Otherwise the box auto-deletes at `expiresAt`. ```typescript await box.delete() ``` *** ## 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. ```typescript await box.files.upload([ { path: "./data/report.csv", destination: "/work/report.csv" }, { path: "./config.json", destination: "/work/config.json" }, ]) ``` 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. ```typescript await box.files.write({ path: "/work/script.js", content: `console.log("hello from box")`, }) ``` *** ### Read files Read the contents of a file as a string. ```typescript const content = await box.files.read("/work/output.json") console.log(JSON.parse(content)) ``` *** ### List files List the entries in a directory. Each entry includes the path, size, type, and last modified timestamp. ```typescript 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..." }, // ] ``` *** ### Download files Pull files from the box back to your local machine. Call with no arguments to download the entire workspace, or pass a `path` to download a specific file or directory. ```typescript await box.files.download() await box.files.download({ path: "/work/output" }) ``` *** ## Examples ### Feed data to an agent Upload input files, run the agent, then read back the structured result. ```typescript 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() ``` ### Inject config before a run Write environment-specific configuration into the box, then let the agent use it. ```typescript 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", }) ``` ### Collect outputs from parallel boxes Fan out work across multiple boxes, then download each result locally. ```typescript 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({ path: "/work" }) await box.delete() }) ) ``` # 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. For a fine-grained token, the following permissions are sufficient for basic usage: * **Contents** — Read and write * **Pull requests** — Read and write ```bash title=".env" {2} UPSTASH_BOX_API_KEY=box_xxxxxxxxxxxxxxxxxxxxxxxx GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxx ``` ```tsx {5-7} import { Agent, Box } from "@upstash/box" const box = await Box.create({ runtime: "node", git: { token: process.env.GITHUB_TOKEN, }, }) ``` ### 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` | ```tsx {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", }, }) ``` 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 ```tsx 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"], }) ``` ### Inspect what changed Use `status()` for a compact summary and `diff()` to diff the repo's uncommitted changes against the initial state. ```tsx const status = await box.git.status() const diff = await box.git.diff() console.log(status) console.log(diff) ``` ### Commit and push After your code changes are ready, create a commit and push the branch. ```tsx 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) ``` ### Open a pull request Create a PR once your branch is pushed. ```tsx 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) ``` *** ## API ### Clone Clones a repository into the current working directory in the box. ```tsx 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", }) ``` ### 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 ```tsx const status = await box.git.status() ``` ### Diff Returns the current Git diff as a string. * Useful to display a patch in your UI * Review what an agent changed ```tsx const diff = await box.git.diff() ``` ### Commit Creates a commit and returns commit information including the SHA and message. ```tsx const commit = await box.git.commit({ message: "feat: add onboarding checklist", }) ``` 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). ```tsx const commit = await box.git.commit({ message: "feat: add onboarding checklist", authorName: "Alice", authorEmail: "alice@acme.com", }) ``` | 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. ```tsx // 👇 push to default branch await box.git.push() // 👇 or to a specific branch await box.git.push({ branch: "feature/onboarding-checklist", }) ``` ### Create a PR Creates a pull request and returns the PR URL and metadata. ```tsx const pr = await box.git.createPR({ title: "feat: add onboarding checklist", body: "Adds a simple onboarding checklist to improve first-run guidance.", base: "main", }) ``` ### Checkout Switches to another branch in the current repository. ```tsx await box.git.checkout({ branch: "release/v1.2.0", }) ``` ### 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. ```tsx const result = await box.git.exec({ args: ["branch", "--show-current"], }) ``` ### 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. ```tsx 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" ``` You can update a single field — the other will not change: ```tsx // Only update the email, keep the existing user name const identity = await box.git.updateConfig({ userEmail: "ci-bot@acme.com", }) ``` 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. ```tsx 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.`, }) ``` ### End-to-end PR automation ```tsx 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", }) ``` # 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` | ```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" }) ``` *** ## 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. 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 ### 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. ```tsx 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) ``` You can also give a box a name and fetch it by name: ```tsx const box = await Box.create({ runtime: "node", name: "my-worker" }) // 👇 fetch by name const sameBox = await Box.getByName("my-worker") ``` 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 @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 | ```ts const box = await Box.create({ size: "large", agent: { harness: Agent.ClaudeCode, model: "anthropic/claude-sonnet-4-5" }, }) console.log(box.size) // "large" ``` Also supported in `Box.fromSnapshot()`: ```ts const box = await Box.fromSnapshot("snap_abc123", { size: "medium" }) ``` ### Check status and inspect activity ```tsx const { status } = await box.getStatus() const logs = await box.logs() const runs = await box.listRuns() ``` 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. ```tsx await box.pause() await box.resume() ``` 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. ```tsx const snapshot = await box.snapshot({ name: "after-setup" }) const restored = await Box.fromSnapshot(snapshot.id) ``` 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: ```tsx await box.delete() ``` 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. 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: ```typescript import { Box } from "@upstash/box" const box = await Box.create({ runtime: "node", size: "medium", keepAlive: true, }) ``` 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. ```typescript const box = await Box.create({ runtime: "node", keepAlive: true, initCommand: "npm install && npm run dev", }) ``` 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: ```typescript await box.setInitCommand("npm run dev") const command = await box.getInitCommand() await box.deleteInitCommand() console.log(box.keepAlive) // true ``` 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: ```ts { mode: "allow-all" } ``` *** ## 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: ```ts type NetworkPolicy = | { mode: "allow-all" | "deny-all" } | { mode: "custom" allowedDomains?: string[] allowedCidrs?: string[] deniedCidrs?: string[] } ``` *** ## Create a box with a policy Pass `networkPolicy` when creating a box: ```ts import { Box } from "@upstash/box" const box = await Box.create({ runtime: "node", networkPolicy: { mode: "custom", allowedDomains: ["api.github.com", "registry.npmjs.org"], }, }) ``` You can also combine domain and CIDR rules: ```ts const box = await Box.create({ runtime: "node", networkPolicy: { mode: "custom", allowedDomains: ["api.github.com", "*.githubusercontent.com"], allowedCidrs: ["104.16.0.0/12"], }, }) ``` `networkPolicy` is also supported in `Box.fromSnapshot()` and `EphemeralBox`. *** ## Read the current policy Use the `networkPolicy` getter: ```ts console.log(box.networkPolicy) // { mode: "allow-all" } ``` *** ## Update a running box Update the policy after creation: ```ts await box.updateNetworkPolicy({ mode: "deny-all" }) ``` Switch back to unrestricted outbound access: ```ts await box.updateNetworkPolicy({ mode: "allow-all" }) ``` 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: ```ts await box.updateNetworkPolicy({ mode: "custom", allowedDomains: ["github.com", "*.github.com", "registry.npmjs.org"], }) ``` Block all outbound traffic: ```ts await box.updateNetworkPolicy({ mode: "deny-all" }) ``` Allow a specific public CIDR: ```ts await box.updateNetworkPolicy({ mode: "custom", allowedCidrs: ["104.16.0.0/12"], }) ``` Block a specific CIDR range: ```ts await box.updateNetworkPolicy({ mode: "custom", deniedCidrs: ["104.16.120.0/24"], }) ``` *** ## 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. ```typescript 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 ``` ### Add authentication Protect your public URL with bearer token or basic authentication. ```typescript // 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..." ``` *** ## API ### Create Public URL Creates a public URL that exposes a port on your box. Returns the URL and authentication credentials if requested. ```typescript const publicUrl = await box.getPublicUrl(3000) console.log(publicUrl.url) // → https://{BOX_ID}-3000.preview.box.upstash.com ``` #### With Bearer Token Add `bearerToken: true` to require an authorization header when accessing the public URL. ```typescript 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 ``` #### With Basic Authentication Add `basicAuth: true` to require username and password when accessing the public URL. ```typescript 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 ``` #### With Both Auth Methods Enable both authentication methods. Either one will work when accessing the public URL. ```typescript 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..." ``` *** ### List Public URLs Get all active public URLs for this box. ```typescript 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 }, // ] ``` *** ### Delete Public URL Remove a public URL by port number. ```typescript await box.deletePublicUrl(3000) ``` *** ## 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. ```typescript // 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 ``` ### 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. ```typescript await box.pause() // This will resume the box const publicUrl = await box.getPublicUrl(3000) ``` *** ## Examples ### Expose an agent-built app Let an agent build a web app, then create a public URL to test it. ```typescript 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" } ``` ### Share a secure public URL Create a public URL with authentication for sharing with team members. ```typescript 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 ``` ### Multi-port application Create multiple public URLs for different services in the same box. ```typescript 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}`) ``` ### Temporary testing public URL Create a public URL, run tests against it, then clean up. ```typescript 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() ``` The SDK still supports `getPreviewUrl`, `listPreviews`, and `deletePreview`, but they are deprecated aliases for `getPublicUrl`, `listPublicUrls`, and `deletePublicUrl`. # 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 **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. Upstash Box is in developer preview — APIs and pricing may change. ### 1. Get your API key Go to the [Upstash Console](https://console.upstash.com) and create an API key. ### 2. Install the SDK ```bash npm npm install @upstash/box ``` ```bash yarn yarn add @upstash/box ``` ```bash pnpm pnpm add @upstash/box ``` ```bash bun bun install @upstash/box ``` ### 3. Set API Key ```bash title=".env" UPSTASH_BOX_API_KEY=box_xxxxxxxxxxxxxxxxxxxxxxxx ``` ### 4. Create a Box ```typescript title="lib/box.ts" import { Box } from "@upstash/box" const box = await Box.create({ runtime: "node", }) ``` By default, runtimes use **Debian** (glibc). For smaller Alpine-based images, use `"node-alpine"`, `"python-alpine"`, etc. 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. 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). *** ### 5. Configure an Agent (optional) To configure an agent for your box, pass the model you want to use and the provider API key: ```typescript {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, }, }) ``` ```typescript {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, }, }) ``` ### 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: ```typescript 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'", }) ``` ### 7. Connect over SSH You can also connect directly to a box shell with SSH: ```bash ssh @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: One box per user with durable state. Personalized agents that remember context and improve over time. Fan out to multiple boxes running specialized agents in parallel, then combine their results. Run the same inputs across isolated boxes and compare model output side by side. *** ## Next Steps Learn the basics about using Upstash Box. Boxes have Claude Code, Codex or OpenCode built-in. # 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. ```typescript 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) ``` | 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. ```typescript 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) ``` | 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. ```typescript const schedules = await box.schedule.list() for (const s of schedules) { console.log(s.id, s.type, s.cron, s.status) } ``` *** ### Get a schedule Retrieve a single schedule by ID. ```typescript 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) ``` *** ### Pause a schedule Pause an active schedule so it stops running. ```typescript await box.schedule.pause("schedule_abc123") ``` *** ### Resume a schedule Resume a paused schedule. ```typescript await box.schedule.resume("schedule_abc123") ``` *** ### Delete a schedule Remove a schedule so it no longer runs. ```typescript await box.schedule.delete("schedule_abc123") ``` *** ## Schedule response Most schedule methods return a `Schedule` object: ```typescript 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 timeout?: number status: "active" | "paused" | "deleted" webhook_url?: string webhook_headers?: Record 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 } ``` *** ## 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. ```typescript 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"], }) ``` ### Daily code review agent Schedule an agent to review recent changes every morning. ```typescript 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" }, }) ``` ### Manage schedules List active schedules, pause, resume, and clean up ones you no longer need. ```typescript 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) ``` # 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. ```tsx const box = await Box.create({ runtime: "node", env: { DATABASE_URL: "postgres://...", ANTHROPIC_API_KEY: "sk-ant-...", }, }) ``` 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. ## 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). ## 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 @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 ``` You can also copy this command from the **SSH** button on the box details page in the Upstash Console. *** ## API ### Run a shell command A shell command resolves when the command finishes and gives you back the result and status. ```typescript const run = await box.exec.command("echo hello") console.log(run.result) // "hello" console.log(run.status) // "completed" ``` If you need additional system tools, install them with Debian's package manager inside the box. ```typescript await box.exec.command("sudo apt-get install ") ``` *** ### Run a code snippet You can execute code inside of a box: ```typescript 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" ``` *** ### Check exit status If the command fails, the run status will be `"failed"` and its result contains the stderr output. ```typescript const run = await box.exec.command("node -e 'process.exit(1)'") console.log(run.status) // "failed" ``` *** ### Chain commands Pass a full shell expression. Pipes, redirects, and chained commands all work as expected. ```typescript const run = await box.exec.command("cd /work && npm install && npm test") console.log(run.result) ``` *** ### Cancel a long-running command You can cancel a run to abort it. The status becomes `"cancelled"`. ```typescript const run = await box.exec.command("sleep 10") await run.cancel() console.log(run.status) // "cancelled" ``` *** ### Retrieve logs After a command finishes, call `logs()` to get the full timestamped output for debugging or auditing. ```typescript 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" }, // ] ``` *** ## Examples ### Install dependencies before a run Set up the environment with `box.exec.command()`, then hand off to the agent. ```typescript 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", }) ``` ### Run a build and extract artifacts Execute a build command, then download the output. ```typescript 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({ path: "/work/dist" }) await box.delete() ``` ### Health-check before handing off Verify the environment is usable before starting a longer agent task. ```typescript 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", }) ``` # 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. 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. *** ## API ### Create a snapshot Call `snapshot()` on a running or paused box. The returned `Snapshot` object contains the ID you need to restore later. ```typescript 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..." // } ``` Snapshots are independent of the source box. Deleting the original box does not delete its snapshots. They remain available for restore at any time. *** ### List snapshots Retrieve all snapshots belonging to a box. ```typescript const snapshots = await box.listSnapshots() console.log(snapshots) // [ // { id: "snap_x7f...", name: "after-setup", status: "ready", ... }, // { id: "snap_a3b...", name: "pre-refactor", status: "ready", ... }, // ] ``` *** ### Delete a snapshot Remove a snapshot by ID. ```typescript await box.deleteSnapshot("snap_x7f...") ``` *** ### 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. ```typescript 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) ``` 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. ```typescript 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", }) } ``` ### Reusable base environments Install dependencies once, snapshot, then spawn boxes from the snapshot to skip setup time. ```typescript import { Agent, Box } from "@upstash/box" const base = await Box.create({ runtime: "node" }) await base.exec("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 }) ) ``` ### Fan-out from a single state Clone a repo once, snapshot, then run different agents or prompts in parallel from the same starting point. ```typescript 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" }), ]) ``` # 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. 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. 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. ```tsx 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")}`, }) ``` 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. 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: ```tsx 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] ``` 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: You can save multiple cards and pick one as the default. Payments are charged from the default card. ## 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: 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 **Prerequisite** You need an Upstash account before subscribing on AWS, create one [here](https://console.upstash.com). 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). Once you click subscribe, you will be prompted to select which personal or team account you wish to link with your AWS Subscription. 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. # 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: 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 Upstash 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. We do not access your information other than: * Your email * Your name * Your profile picture and we never share your information with third parties. # 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: 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. ## Create Team You can create a team using the menu `Account > Teams`
> 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. ## 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`. Simply enter their email addresses.It's not an issue if the email addresses are not yet registered with Upstash. Once the user registers with that email, they will gain access to the team. We do not send invitations; when you add a member, they become a member directly. You can also remove members from the same page. > Only Admins or the Owner can add/remove users. ## 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`. # 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. ### 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**: **Write commands go to the primary database** for consistency. After a successful write, they are replicated to all read replicas: *** ## 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: **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. ## 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 Upstash 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. We do not access your information other than: * Your email * Your name * Your profile picture and we never share your information with third parties. 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. ## Teams ### Create Team You can create a team using the menu `Account > Teams`
> 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. ### 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. ### 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`. # 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.
#### QStash Price Decrease (Sep 15, 2022) The price is \$1 per 100K requests.
#### [Pulumi Provider is available](https://upstash.com/blog/upstash-pulumi-provider) (August 4, 2022)
#### [QStash is released and announced](https://upstash.com/blog/qstash-announcement) (July 18, 2022)
#### [Announcing Upstash CLI](https://upstash.com/blog/upstash-cli) (May 16, 2022)
#### [Introducing Redis 6 Compatibility](https://upstash.com/blog/redis-6) (April 10, 2022)
#### 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.
#### [Announcing Upstash Redis SDK v1.0.0](https://upstash.com/blog/upstash-redis-sdk-v1) (March 14, 2022)
#### 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).
#### 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.
#### 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 Prod Pack is available as a $200/month add-on per database for all paid plans except Free tier. ### 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 Disabling this feature will permanently revoke current credentials and generate new ones. ### 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: To be able to see these options in chat, remember to sign into your Upstash Account first. 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. The Developer API is only available to native Upstash accounts. Accounts created via third-party platforms like Vercel or Fly.io are not supported. ### 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. 3. Enter a name for your key. You can not use the same name for multiple keys. 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.
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. # 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{ "": pulumi.String("owner"), "": 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 ```hcl example.tf data "upstash_qstash_endpoint_data" "exampleQStashEndpointData" { endpoint_id = resource.upstash_qstash_endpoint.exampleQStashEndpoint.endpoint_id } ``` ## Schema ### Required Topic Id that the endpoint is added to ### Read-Only Unique QStash Endpoint ID The ID of this resource. Unique QStash Topic Name for Endpoint # upstash_qstash_schedule_data Source: https://upstash.com/docs/devops/terraform/data_sources/upstash_qstash_schedule_data ```hcl example.tf data "upstash_qstash_schedule_data" "exampleQStashScheduleData" { schedule_id = resource.upstash_qstash_schedule.exampleQStashSchedule.schedule_id } ``` ## Schema ### Required Unique QStash Schedule ID for requested schedule ### Read-Only Body to send for the POST request in string format. Needs escaping () double quotes. Creation time for QStash Schedule Cron string for QStash Schedule Destination for QStash Schedule. Either Topic ID or valid URL Forward headers to your API The ID of this resource. Start time for QStash Scheduling. Retries for QStash Schedule requests. # upstash_qstash_topic_data Source: https://upstash.com/docs/devops/terraform/data_sources/upstash_qstash_topic_data ```hcl example.tf data "upstash_qstash_topic_data" "exampleQstashTopicData" { topic_id = resource.upstash_qstash_topic.exampleQstashTopic.topic_id } ``` ## Schema ### Required Unique QStash Topic ID for requested topic ### Read-Only Endpoints for the QStash Topic The ID of this resource. Name of the QStash Topic # upstash_redis_database_data Source: https://upstash.com/docs/devops/terraform/data_sources/upstash_redis_database_data ```hcl example.tf data "upstash_redis_database_data" "exampleDBData" { database_id = resource.upstash_redis_database.exampleDB.database_id } ``` ## Schema ### Required Unique Database ID for created database ### Read-Only Upgrade to higher plans automatically when it hits quotas Creation time of the database Name of the database Type of the database Daily bandwidth limit for the database Disk threshold for the database Max clients for the database Max commands per second for the database Max entry size for the database Max request size for the database Memory threshold for the database Database URL for connection The ID of this resource. Password of the database Port of the endpoint 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]) Rest Token for the database. 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.) Region of the database. Possible values are: `global`, `eu-west-1`, `us-east-1`, `us-west-1`, `ap-northeast-1` , `eu-central1` Rest Token for the database. State of the database When enabled, data is encrypted in transit. (If changed to false from true, results in deletion and recreation of the resource) User email for the database # upstash_team_data Source: https://upstash.com/docs/devops/terraform/data_sources/upstash_team_data ```hcl example.tf data "upstash_team_data" "teamData" { team_id = resource.upstash_team.exampleTeam.team_id } ``` ## Schema ### Required Unique Cluster ID for created cluster ### Read-Only Whether Credit Card is copied The ID of this resource. Members of the team. (Owner must be specified, which is the owner of the api key.) Name of the team # 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 ``` 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 ```hcl example.tf resource "upstash_qstash_endpoint" "exampleQStashEndpoint" { url = "https://***.***" topic_id = resource.upstash_qstash_topic.exampleQstashTopic.topic_id } ``` ## Schema ### Required Topic ID that the endpoint is added to URL of the endpoint ### Read-Only Unique QStash endpoint ID The ID of this resource. Unique QStash topic name for endpoint # upstash_qstash_schedule Source: https://upstash.com/docs/devops/terraform/resources/upstash_qstash_schedule ```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://***.***" } ``` ## Schema ### Required Cron string for QStash Schedule Destination for QStash Schedule. Either Topic ID or valid URL ### Optional Body to send for the POST request in string format. Needs escaping () double quotes. Callback URL for QStash Schedule. Content based deduplication for QStash Scheduling. Content type for QStash Scheduling. Deduplication ID for QStash Scheduling. Delay for QStash Schedule. Forward headers to your API Start time for QStash Scheduling. Retries for QStash Schedule requests. ### Read-Only Creation time for QStash Schedule. The ID of this resource. Unique QStash Schedule ID for requested schedule # upstash_qstash_topic Source: https://upstash.com/docs/devops/terraform/resources/upstash_qstash_topic ```hcl example.tf resource "upstash_qstash_topic" "exampleQStashTopic" { name = "exampleQStashTopicName" } ``` ## Schema ### Required Name of the QStash topic ### Read-Only Endpoints for the QStash topic The ID of this resource. Unique QStash topic ID for requested topic # upstash_redis_database Source: https://upstash.com/docs/devops/terraform/resources/upstash_redis_database ```hcl example.tf resource "upstash_redis_database" "exampleDB" { database_name = "Terraform DB6" region = "eu-west-1" tls = "true" multizone = "true" } ``` ## Schema ### Required Name of the database Region of the database. Possible values are: `global`, `eu-west-1`, `us-east-1`, `us-west-1`, `ap-northeast-1` , `eu-central1` ### Optional Upgrade to higher plans automatically when it hits quotas Enable eviction, to evict keys when your database reaches the max size 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]) 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.) When enabled, data is encrypted in transit. (If changed to false from true, results in deletion and recreation of the resource) ### Read-Only Creation time of the database Unique Database ID for created database Type of the database Daily bandwidth limit for the database Disk threshold for the database Max clients for the database Max commands per second for the database Max entry size for the database Max request size for the database Memory threshold for the database Database URL for connection The ID of this resource. Password of the database Port of the endpoint Rest Token for the database. Rest Token for the database. State of the database User email for the database # upstash_team Source: https://upstash.com/docs/devops/terraform/resources/upstash_team ```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", } } ``` ## Schema ### Required Whether Credit Card is copied Members of the team. (Owner must be specified, which is the owner of the api key.) Name of the team ### Read-Only The ID of this resource. Unique Cluster ID for created cluster # 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 Create a Redis Database within seconds Create a Vector Database for AI & LLMs Publish your first message Write durable serverless functions ## Concepts Upstash is serverless. You don't need to provision any infrastructure. Just create a database and start using it. Price scales to zero. You don't pay for idle or unused resources. You pay only for what you use. Upstash Redis replicates your data for the best latency all over the world. Upstash REST APIs enable access from all types of runtimes. ## Get In touch Follow us on X for the latest news and updates. Join our Discord Community and ask your questions to the team and other developers. # 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 " ``` ## 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= ``` 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. 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). 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 { 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. 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. ## 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. 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 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: ```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 (
); } ``` ```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 }); } ```
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: Background job diagram 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. You can publish to destination, URL Group or queue in the same batch request. ## Batching messages with destinations You can also send messages to the same destination! ```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: "" }); const res = await client.batchJSON([ { url: "https://example.com/destination1", }, { url: "https://example.com/destination2", }, ]); ``` ```python Python from qstash import QStash client = QStash("") client.message.batch_json( [ {"url": "https://example.com/destination1"}, {"url": "https://example.com/destination2"}, ] ) ``` ## 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. ```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: "" }); // 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("") client.message.batch_json( [ {"url_group": "my-url-group"}, {"url": "https://example.com/destination2"}, ] ) ``` ## 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. ```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: "" }); 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("") 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) ``` ## Batching messages with headers and body You can provide custom headers and a body for each message in the batch. ```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: "" }); // 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("") 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"}, }, ] ) ``` #### 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 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. 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. ```bash cURL curl -X POST \ https://qstash.upstash.io/v2/publish/https://my-api... \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -H 'Upstash-Callback: ' \ -d '{ "hello": "world" }' ``` ```typescript Typescript import { Client } from "@upstash/qstash"; const client = new Client({ 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("") client.message.publish_json( url="https://my-api...", body={ "hello": "world", }, callback="https://my-callback...", ) ``` 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. ```bash cURL curl -X POST \ https://qstash.upstash.io/v2/publish/ \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -H 'Upstash-Failure-Callback: ' \ -d '{ "hello": "world" }' ``` ```typescript Typescript import { Client } from "@upstash/qstash"; const client = new Client({ 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("") client.message.publish_json( url="https://my-api...", body={ "hello": "world", }, failure_callback="https://my-callback...", ) ``` 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. 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) 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. ```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: "" }); const res = await client.publishJSON({ url: "https://my-api...", body: { hello: "world" }, deduplicationId: "abcdef", }); ``` ```python Python from qstash import QStash client = QStash("") client.message.publish_json( url="https://my-api...", body={ "hello": "world", }, deduplication_id="abcdef", ) ``` ## Content Based Deduplication If you want to deduplicate messages automatically, you can set the `Upstash-Content-Based-Deduplication` header to `true`. ```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: "" }); const res = await client.publishJSON({ url: "https://my-api...", body: { hello: "world" }, contentBasedDeduplication: true, }); ``` ```python Python from qstash import QStash client = QStash("") client.message.publish_json( url="https://my-api...", body={ "hello": "world", }, content_based_deduplication=True, ) ``` 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). The deduplication window is 10 minutes. After that, messages with the same ID or content can be sent again. # 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 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)**. ## 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 ``. 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. ```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: "" }); const res = await client.publishJSON({ url: "https://my-api...", body: { hello: "world" }, delay: 60, }); ``` ```python Python from qstash import QStash client = QStash("") client.message.publish_json( url="https://my-api...", body={ "hello": "world", }, headers={ "test-header": "test-value", }, delay="60s", ) ``` `Upstash-Delay` will get overridden by `Upstash-Not-Before` header when both are used together. ## 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. ```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: "" }); const res = await client.publishJSON({ url: "https://my-api...", body: { hello: "world" }, notBefore: 1657104947, }); ``` ```python Python from qstash import QStash client = QStash("") client.message.publish_json( url="https://my-api...", body={ "hello": "world", }, headers={ "test-header": "test-value", }, not_before=1657104947, ) ``` `Upstash-Not-Before` will override the `Upstash-Delay` header when both are used together. ## 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. 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). 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. There are no limits to number of keys you can use. 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. ```typescript TypeScript const client = new Client({ 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!"}' ``` 🎉 That's it! From now on, QStash will enforce these limits by counting the number of messages associated with this flow-control key. ## 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: ```typescript TypeScript const client = new Client({ 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!"}' ``` ## 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: ```typescript TypeScript const client = new Client({ 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!"}' ``` 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. ```typescript TypeScript const client = new Client({ 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!"}' ``` ## 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. ```typescript TypeScript import { Client } from "@upstash/qstash"; const client = new Client({ 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("") 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 " ``` 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. ```typescript TypeScript import { Client } from "@upstash/qstash"; const client = new Client({ 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("") # 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 " # Resume delivery curl -X POST https://qstash.upstash.io/v2/flowControl/USER_GIVEN_KEY/resume \ -H "Authorization: Bearer " ``` ### 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. ```typescript TypeScript import { Client } from "@upstash/qstash"; const client = new Client({ 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("") # 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 " # Unpin specific configs curl -X POST "https://qstash.upstash.io/v2/flowControl/USER_GIVEN_KEY/unpin?parallelism=true&rate=true" \ -H "Authorization: Bearer " ``` 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. ```typescript TypeScript import { Client } from "@upstash/qstash"; const client = new Client({ token: "" }); await client.flowControl.resetRate("USER_GIVEN_KEY"); ``` ```python Python from qstash import QStash client = QStash("") 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 " ``` ### Get global parallelism Returns the global parallelism usage across all flow control keys. ```typescript TypeScript import { Client } from "@upstash/qstash"; const client = new Client({ token: "" }); const info = await client.flowControl.getGlobalParallelism(); console.log(info); // { // parallelismMax: 500, // parallelismCount: 42 // } ``` ```python Python from qstash import QStash client = QStash("") 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 " ``` | 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. 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. ```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: "" }); 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("") client.message.enqueue_json( queue="my-queue", url="https://example.com", body={ "Hello": "World", }, ) ``` ## Controlled Parallelism 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. 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: ```bash cURL curl -XPOST https://qstash.upstash.io/v2/queues/ \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "queueName": "my-queue", "parallelism": 5, }' ``` ```typescript TypeScript const client = new Client({ token: "" }); const queue = client.queue({ queueName: "my-queue" }) await queue.upsert({ parallelism: 1, }) ``` ```python Python from qstash import QStash client = QStash("") client.queue.upsert("my-queue", parallelism=5) ``` After that, you can use the `enqueue` path to send your messages. ```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: "" }); 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("") client.message.enqueue_json( queue="my-queue", url="https://example.com", body={ "Hello": "World", }, ) ``` You can check the parallelism of your queues with the following API: ```bash cURL curl https://qstash.upstash.io/v2/queues/my-queue \ -H "Authorization: Bearer " ``` ```typescript TypeScript const client = new Client({ token: "" }); const queue = client.queue({ queueName: "my-queue" }) const res = await queue.get() ``` ```python Python from qstash import QStash client = QStash("") client.queue.get("my-queue") ``` # Retry Source: https://upstash.com/docs/qstash/features/retry 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 QStash pricing page. 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. The maximum number of retries depends on your current plan. By default, we retry the maximum amount of times, but you can set it lower by sending the `Upstash-Retries` header: ```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: "" }); const res = await client.publishJSON({ url: "https://my-api...", body: { hello: "world" }, retries: 2, }); ``` ```python Python from qstash import QStash client = QStash("") client.message.publish_json( url="https://my-api...", body={ "hello": "world", }, retries=2, ) ``` 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. ```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: "" }); 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("") client.message.publish_json( url="https://my-api...", body={ "hello": "world", }, retries=3, retry_delay="pow(2, retried) * 1000", # 2^retried * 1000ms ) ``` 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: ```typescript Typescript import { Client } from "@upstash/qstash"; const client = new Client({ token: "" }); await client.schedules.create({ destination: "https://example.com", cron: "* * * * *", }); ``` ```python Python from qstash import QStash client = QStash("") 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' ``` All of the [other config options](/docs/qstash/howto/publishing#optional-parameters-and-configuration) can still be used. It can take up to 60 seconds for the schedule to be loaded on an active node and triggered for the first time. 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: ```typescript Typescript import { Client } from "@upstash/qstash"; const client = new Client({ token: "" }); await client.schedules.create({ destination: "urlGroupName", cron: "* * * * *", }); ``` ```python Python from qstash import QStash client = QStash("") 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/' ``` ### Scheduling to a Queue You can schedule an item to be added to a queue at a specified time. ```typescript Typescript import { Client } from "@upstash/qstash"; const client = new Client({ 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' ``` ### 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. ```typescript Typescript import { Client } from "@upstash/qstash"; const client = new Client({ 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' ``` ### 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: ```typescript Typescript import { Client } from "@upstash/qstash"; const client = new Client({ 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("") 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' ``` # Security Source: https://upstash.com/docs/qstash/features/security ### Request Authorization When interacting with the QStash API, you will need an authorization token. You can get your token from the [Console](https://console.upstash.com/qstash). Send this token along with every request made to `QStash` inside the `Authorization` header like this: ``` "Authorization": "Bearer " ``` ### 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. Only the last 10.000 logs are kept and older logs are removed automatically. ## 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) 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). ```shell cURL curl -XDELETE \ -H 'Authorization: Bearer XXX' \ 'https://qstash.upstash.io/v2/schedules/' ``` ```typescript Typescript import { Client } from "@upstash/qstash"; const client = new Client({ token: "" }); await client.schedules.delete(""); ``` ```python Python from qstash import QStash client = QStash("") client.schedule.delete("") ``` 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). ```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: "" }); const allSchedules = await client.schedules.list(); ``` ```python Python from qstash import QStash client = QStash("") client.schedule.list() ``` # 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). ```bash cURL curl -X POST \ https://qstash.upstash.io/v2/publish/ \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -H 'Upstash-Failure-Callback: ' \ -d '{ "hello": "world" }' ``` ```typescript Typescript import { Client } from "@upstash/qstash"; const client = new Client({ 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("") client.message.publish_json( url="https://my-api...", body={ "hello": "world", }, failure_callback="https://my-callback...", ) ``` ## 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). 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. The development server fully supports all QStash features including Schedules, URL Groups, Workflows, and Event Logs. Since the development server operates entirely in-memory, all data is reset when the server restarts. ## 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. Dev mode is automatically a no-op when `NODE_ENV=production` and in browser/edge runtimes. ### 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. ### 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: ```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" ``` ## 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` ## 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 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. After migration, your app will be able to handle requests from both regions simultaneously to ensure a smooth transition. ## 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 If a message was published in one region, it will still be delivered from that region after the migration. 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" ``` 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. #### 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. Destination URLs must always include the protocol (`http://` or `https://`) ## 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 ```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: "" }); 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("") client.message.publish_json( url="https://my-api...", body={ "hello": "world", }, headers={ "my-header": "my-value", }, ) ``` 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 ``` ```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: "" }); const res = await client.publishJSON({ urlGroup: "my-url-group", body: { "hello": "world" }, }); ``` ``` python Python from qstash import QStash client = QStash("") client.message.publish_json( url_group="my-url-group", body={ "hello": "world", }, ) ``` ## 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. # 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:` 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]`) | Redaction is one-way. Once a field is redacted, the original value cannot be retrieved from the API or dashboard. ```typescript TypeScript import { Client } from "@upstash/qstash"; const client = new Client({ 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("") 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...' ``` 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. ```typescript TypeScript import { Client } from "@upstash/qstash"; const client = new Client({ 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("") 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' ``` 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:` — which is the hash value visible in the dashboard, not the original data. # 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() ``` 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. ## 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. ![]() 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. ## 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. ```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("", "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 ``` 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. ### 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). ```bash cURL curl -XPOST https://qstash.upstash.io/v2/topics/:urlGroupName/endpoints \ -H "Authorization: Bearer " \ -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: "" }); 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("") client.url_group.upsert_endpoints( url_group="url-group-name", endpoints=[ {"name": "endpoint1", "url": "https://example.com"}, {"name": "endpoint2", "url": "https://somewhere-else.com"}, ], ) ``` # 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= ` 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. 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. ## 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/?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/ \ -H "Authorization: Bearer " -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: "" }); await client.publishJSON({ api: { name: "llm", provider: 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: "" }); const result = await client.queue({ queueName: "your-queue-name" }).enqueueJSON({ api: { name: "llm", provider: 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: "" }); const result = await client.batchJSON([ { api: { name: "llm", provider: 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: "" }) }, 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: "" }), 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. **Integration Scope** Upstash Datadog Integration covers Prod Pack. ## **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 ```js JavaScript import { Client, upstash } from "@upstash/qstash"; const client = new Client({ 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("") result = q.message.publish_json( api={"name": "llm", "provider": openai("")}, body={ "model": "gpt-3.5-turbo", "messages": [ { "role": "user", "content": "Write a hello world program in Rust.", } ], }, callback="https://abc.requestcatcher.com/", ) print(result) ``` ### Enqueueing a Chat Completion Request ```js JavaScript import { Client, upstash } from "@upstash/qstash"; const client = new Client({ 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("") result = q.message.enqueue_json( queue="queue-name", api={"name": "llm", "provider": openai("")}, body={ "model": "gpt-3.5-turbo", "messages": [ { "role": "user", "content": "Write a hello world program in Rust.", } ], }, callback="https://abc.requestcatcher.com", ) print(result) ``` ### Sending Chat Completion Requests in Batches ```js JavaScript import { Client, upstash } from "@upstash/qstash"; const client = new Client({ 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("") result = q.message.batch_json( [ { "api":{"name": "llm", "provider": openai("")}, "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" }, ... ]' ``` ### 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: "", }); 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 *** ### 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) 3. Copy the cURL snippet created for you, representing your request. 4. Back to the n8n, in HTTP Request Parameters tab, use import cURL. 5. Paste the cURL snippet that you copied in the console, and let n8n to fill out the form for you. *** ### 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. # 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: **Integration Scope** Upstash Prometheus Integration covers Prod Pack. ## **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. **Header Format** Send the token as `Authorization: Bearer `. ![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 Test and Save. ![datasource-final.png]() ## **Step 4: Import Dashboard** You can use the Upstash Grafana dashboard to visualize QStash metrics. Open the import dialog and use: Upstash QStash Dashboard ![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: "" }); await client.publishJSON({ api: { name: "email", provider: resend({ token: "" }), }, body: { from: "Acme ", to: ["delivered@resend.dev"], subject: "Hello World", html: "

It works!

", }, }); ``` 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: "", batch: true }), }, body: [ { from: "Acme ", to: ["foo@gmail.com"], subject: "Hello World", html: "

It works!

", }, { from: "Acme ", to: ["bar@outlook.com"], subject: "World Hello", html: "

It works!

", }, ], }); ``` 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. ```shell curl -XPOST \ -H 'Authorization: Bearer XXX' \ -H "Content-type: application/json" \ -d '{ "hello": "world" }' \ 'https://qstash.upstash.io/v2/publish/https://example.com' ``` ```typescript const client = new Client({ token: "" }); await client.publishJSON({ url: "https://example.com", body: { hello: "world", }, }); ``` ```python from qstash import QStash client = QStash("") client.message.publish_json( url="https://example.com", body={ "hello": "world", }, ) # Async version is also available ``` ### 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. ```shell curl -XPOST \ -H 'Authorization: Bearer XXX' \ -H "Content-type: application/json" \ -d '{ "hello": "world" }' \ 'https://qstash.upstash.io/v2/publish/myUrlGroup' ``` ```typescript const client = new Client({ token: "" }); await client.publishJSON({ urlGroup: "myUrlGroup", body: { hello: "world", }, }); ``` ```python from qstash import QStash client = QStash("") client.message.publish_json( url_group="my-url-group", body={ "hello": "world", }, ) # Async version is also available ``` ### 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. ```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' ``` ```typescript const client = new Client({ token: "" }); await client.publishJSON({ url: "https://example.com", body: { hello: "world", }, delay: 300, }); ``` ```python from qstash import QStash client = QStash("") client.message.publish_json( url="https://example.com", body={ "hello": "world", }, delay="5m", ) # Async version is also available ``` ### Send a custom header Add a custom header to the message to be published. ```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' ``` ```typescript const client = new Client({ token: "" }); await client.publishJSON({ url: "https://example.com", body: { hello: "world", }, headers: { "My-Header": "my-value", }, }); ``` ```python from qstash import QStash client = QStash("") client.message.publish_json( url="https://example.com", body={ "hello": "world", }, headers={ "My-Header": "my-value", }, ) # Async version is also available ``` ### Schedule to run once a day ```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' ``` ```typescript const client = new Client({ token: "" }); await client.schedules.create({ destination: "https://example.com", cron: "0 0 * * *", }); ``` ```python from qstash import QStash client = QStash("") client.schedule.create( destination="https://example.com", cron="0 0 * * *", ) # Async version is also available ``` ### 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. ```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!"}' ``` ```typescript const client = new Client({ token: "" }); const queue = client.queue({ queueName: "my-queue" }) await queue.enqueueJSON({ url: "https://example.com", body: { "Hello": "World" } }) ``` ```python from qstash import QStash client = QStash("") client.message.enqueue_json( queue="my-queue", url="https://example.com", body={ "Hello": "World", }, ) # Async version is also available ``` ### Publish messages in a [batch](/docs/qstash/features/batch) Publish multiple messages in a single request. ```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" } ]' ``` ```typescript import { Client } from "@upstash/qstash"; const client = new Client({ token: "" }); const res = await client.batchJSON([ { url: "https://example.com/destination1", }, { url: "https://example.com/destination2", }, ]); ``` ```python from qstash import QStash client = QStash("") client.message.batch_json( [ { "url": "https://example.com/destination1", }, { "url": "https://example.com/destination2", }, ] ) # Async version is also available ``` ### 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). ```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' ``` ```typescript const client = new Client({ token: "" }); await client.publishJSON({ url: "https://example.com", body: { hello: "world", }, retries: 3, }); ``` ```python from qstash import QStash client = QStash("") client.message.publish_json( url="https://example.com", body={ "hello": "world", }, retries=3, ) # Async version is also available ``` ### 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). ```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' ``` ```typescript const client = new Client({ token: "" }); await client.publishJSON({ url: "https://example.com", body: { hello: "world", }, retries: 3, retryDelay: "pow(2, retried) * 1000", // 2^retried * 1000ms }); ``` ```python from qstash import QStash client = QStash("") 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 ``` **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. ```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' ``` ```typescript const client = new Client({ token: "" }); await client.publishJSON({ url: "https://example.com", body: { hello: "world", }, callback: "https://example.com/callback", failureCallback: "https://example.com/failure", }); ``` ```python from qstash import QStash client = QStash("") 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 ``` ### Get message logs Retrieve logs for all messages that have been published (filtering is also available). ```shell curl https://qstash.upstash.io/v2/logs \ -H "Authorization: Bearer XXX" ``` ```typescript const client = new Client({ token: "" }); const logs = await client.logs() ``` ```python from qstash import QStash client = QStash("") client.event.list() # Async version is also available ``` ### List all schedules ```shell curl https://qstash.upstash.io/v2/schedules \ -H "Authorization: Bearer XXX" ``` ```typescript const client = new Client({ token: "" }); const scheds = await client.schedules.list(); ``` ```python from qstash import QStash client = QStash("") client.schedule.list() # Async version is also available ``` # Changelog Source: https://upstash.com/docs/qstash/overall/changelog 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. * **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. * **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. * **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](http://localhost:3000/qstash/howto/local-development) for details. * **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). * No new features for QStash this month. We are mostly focused on stability and performance. * **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. * **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. * **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. * **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). * **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. * **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). * **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. * **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. * 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). * 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). * 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. * 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 * [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 * [Queues and parallelism](/docs/qstash/features/queues) * [Event filtering](/docs/qstash/api-reference/logs/list-logs) * [Batch publish messages](/docs/qstash/api-reference/messages/batch-messages) * [Bulk delete](/docs/qstash/api-reference/dlq/bulk-delete-dlq-messages) for DLQ * 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) * 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) * Added [Upstash-Failure-Callback](/docs/qstash/features/callbacks#what-is-a-failure-callback) # 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 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. 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. ## Quick Start Check out these Quick Start guides to get started with QStash in your application. Build a Next application that uses QStash to start a long-running job on your platform Build a Python application that uses QStash to schedule a daily job that clean up a database Or continue reading to learn how to send your first message! ## Send your first message **Prerequisite** You need an Upstash account before publishing messages, create one [here](https://console.upstash.com). ### 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 token Go to the [Upstash Console](https://console.upstash.com/qstash) and copy the `QSTASH_TOKEN`. ### 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). ```bash cURL curl -XPOST \ -H 'Authorization: Bearer ' \ -H "Content-type: application/json" \ -d '{ "hello": "world" }' \ 'https://qstash.upstash.io/v2/publish/https://' ``` ```bash cURL RequestCatcher curl -XPOST \ -H 'Authorization: Bearer ' \ -H "Content-type: application/json" \ -d '{ "hello": "world" }' \ 'https://qstash.upstash.io/v2/publish/https://firstqstashmessage.requestcatcher.com/test' ``` 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. ### 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. Learn more about different states [here](/docs/qstash/howto/debug-logs). ## Features and Use Cases Run long-running tasks in the background, without blocking your application Schedule messages to be delivered at a time in the future Publish messages to multiple endpoints, in parallel, using URL Groups Enqueue messages to be delivered one by one in the order they have enqueued. Custom rate per second and parallelism limits to avoid overflowing your endpoint. Get a response delivered to your API when a message is delivered Use a Dead Letter Queue to have full control over failed messages Prevent duplicate messages from being delivered Publish, enqueue, or batch chat completion requests using large language models with QStash features. # 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 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. # Use Cases Source: https://upstash.com/docs/qstash/overall/usecases TODO: andreas: rework and reenable this page after we have 2 use cases ready https://linear.app/upstash/issue/QSTH-84/use-cases-summaryhighlights-of-recipes This section is still a work in progress. We will be adding detailed tutorials for each use case soon. Tell us on [Discord](https://discord.gg/w9SenAtbme) or [X](https://x.com/upstash) what you would like to see here. ### Triggering Nextjs Functions on a schedule Create a schedule in QStash that runs every hour and calls a Next.js serverless function hosted on Vercel. ### Reset Billing Cycle in your Database Once a month, reset database entries to start a new billing cycle. ### Fanning out alerts to Slack, email, Opsgenie, etc. Createa QStash URL Group that receives alerts from a single source and delivers them to multiple destinations. ### Send delayed message when a new user signs up Publish delayed messages whenever a new user signs up in your app. After a certain delay (e.g. 10 minutes), QStash will send a request to your API, allowing you to email the user a welcome message. - [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("") client.message.publish_json(...) ``` #### Asynchronous Client ```python import asyncio from qstash import AsyncQStash async def main(): client = AsyncQStash("") 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( "", 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: "", }); ``` #### 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: "", 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: "", 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 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 }) {}, }) ``` Broadcast to all users in a room: ```typescript route.ts await realtime.channel(`room-${roomId}`).emit("room.message", { text: "Hello everyone!", sender: "Alice", }) ``` Scope events to team workspaces: ```typescript route.ts await realtime.channel(`team-${teamId}`).emit("project.update", { project: "Website Redesign", status: "In Progress", }) ``` ## 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(["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 (

Active channels: {channels.join(", ")}

) } ``` ## 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 }) } } }, }) ``` See the middleware documentation for authentication examples # 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 {children} } ``` ```tsx layout.tsx import { Providers } from "./providers" export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` ### 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() ``` ## 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

Listening for events...

} ``` ## Provider Options API configuration: - `url`: The realtime endpoint URL - `withCredentials`: Whether to send cookies with requests Maximum number of reconnection attempts before giving up ```tsx providers.tsx "use client" import { RealtimeProvider } from "@upstash/realtime/client" export function Providers({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` ## Hook Options Array of event names to subscribe to (e.g. `["notification.alert", "chat.message"]`) Callback when an event is received. Receives an object with `event`, `data`, and `channel`. Array of channel names to subscribe to Whether the subscription is active. Set to `false` to disconnect. ## Return Value The hook returns an object with: Current connection state: `"connecting"`, `"connected"`, `"disconnected"`, or `"error"` ```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 (

Status: {status}

) } ``` ### 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

Notifications {user ? "enabled" : "disabled"}

} ``` ## 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

Listening to multiple events

} ``` ## 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

Listening to multiple channels

} ``` ### 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(["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 (

Active channels: {channels.join(", ")}

) } ``` ## 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 ( {children} ) } ``` ## Use Cases 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

Listening for notifications...

} ```
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 export default function Chat() { const [messages, setMessages] = useState([]) useRealtime({ channels: ["room-123"], events: ["chat.message"], onData({ data }) { setMessages((prev) => [...prev, data]) }, }) return (
{messages.map((msg, i) => (

{msg.sender}: {msg.text}

))}
) } ```
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 (

Active Users: {metrics?.users}

Revenue: ${metrics?.revenue}

) } ```
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