yaak Documentation

The most intuitive desktop API client. Organize and execute REST, GraphQL, WebSockets, Server Sent Events, and gRPC ๐Ÿฆฌ

RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy yaak on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name yaak -p 8080:8080 --restart always mountain-loop/yaak:latest

Technical Documentation: mountain-loop/yaak

โ„น๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference

Overview (`README.md`)

๐Ÿ’ซ Yaak โžŸ Desktop API Client ๐Ÿ’ซ

Install

Download Yaak for macOS, Windows, or Linux from yaak.app/download. Package managers and other install methods are in the installation docs.

Features

Yaak is an offline-first API client designed to stay out of your way while giving you everything you need when you need it.
Built with Tauri, Rust, and React, itโ€™s fast, lightweight, and private. No telemetry, no VC funding, and no cloud lock-in.

๐ŸŒ Work with any API

  • Import collections from Postman, Insomnia, OpenAPI, Swagger, or Curl.
  • Send requests via REST, GraphQL, gRPC, WebSocket, or Server-Sent Events.
  • Filter and inspect responses with JSONPath or XPath.

๐Ÿ” Stay secure

  • Use OAuth 2.0, JWT, Basic Auth, or custom plugins for authentication.
  • Secure sensitive values with encrypted secrets.
  • Store secrets in your OS keychain.

โ˜๏ธ Organize & collaborate

  • Group requests into workspaces and nested folders.
  • Use environment variables to switch between dev, staging, and prod.
  • Mirror workspaces to your filesystem for versioning in Git or syncing with Dropbox.

๐Ÿงฉ Extend & customize

  • Insert dynamic values like UUIDs or timestamps with template tags.
  • Pick from built-in themes or build your own.
  • Create plugins to extend authentication, template tags, or the UI.

Contribution Policy

[!IMPORTANT]
Community PRs are currently limited to bug fixes.
If your PR is not a bug fix, link the feedback item where @gschier explicitly gave you permission to work on it.
See CONTRIBUTING.md for policy details and DEVELOPMENT.md for local setup.

Useful Resources

License

The source is MIT licensed and can be built and run for free, for both personal and commercial use.

The prebuilt binaries follow Yaak's pricing terms: free for personal use, license required for commercial use.


Overview (`crates-cli/yaak-cli/README.md`)

Yaak CLI

The yaak CLI for publishing plugins and creating/updating/sending requests.

Installation

sh
npm install @yaakapp/cli

Agentic Workflows

The yaak CLI is primarily meant to be used by AI agents, and has the following features:

  • schema subcommands to get the JSON Schema for any model (eg. yaak request schema http)
  • --json '{...}' input format to create and update data
  • --verbose mode for extracting debug info while sending requests
  • The ability to send entire workspaces and folders (Supports --parallel and --fail-fast)

Agent Setup

Install the Yaak skill so your coding agent knows how to drive the CLI:

sh
yaak agent install                                  # every agent found on this machine
npx skills add mountain-loop/yaak --skill use-yaak  # or with the skills CLI

Claude Code and Codex can install it as a plugin instead. In Claude Code, run
/plugin install yaak --marketplace mountain-loop/yaak; the plugin also registers the
yaak mcp server. In Codex, run codex plugin marketplace add mountain-loop/yaak,
then codex plugin add yaak@yaak.

Any MCP client can run the server over stdio:

json
{
  "mcpServers": {
    "yaak": { "command": "npx", "args": ["-y", "@yaakapp/cli", "mcp"] }
  }
}

Example Prompts

Use the yaak CLI with agents like Claude or Codex to do useful things for you.

Here are some example prompts:

text
Scan my API routes and create a workspace (using yaak cli) with
all the requests needed for me to do manual testing?
text
Send all the GraphQL requests in my workspace

Description

Here's the current print of yaak --help

text
Yaak CLI - API client from the command line

Usage: yaak [OPTIONS] <COMMAND>

Commands:
  auth         Authentication commands
  plugin       Plugin development and publishing commands
  send         Send a request, folder, or workspace by ID
  workspace    Workspace commands
  request      Request commands
  folder       Folder commands
  environment  Environment commands

Options:
      --data-dir <DATA_DIR>        Use a custom data directory
  -e, --environment <ENVIRONMENT>  Environment ID to use for variable substitution
  -v, --verbose                    Enable verbose send output (events and streamed response body)
      --log [<LEVEL>]              Enable CLI logging; optionally set level (error|warn|info|debug|trace) [possible values: error, warn, info, debug, trace]
  -h, --help                       Print help
  -V, --version                    Print version

Agent Hints:
  - Template variable syntax is ${[ my_var ]}, not {{ ... }}
  - Template function syntax is ${[ namespace.my_func(a='aaa',b='bbb') ]}
  - View JSONSchema for models before creating or updating (eg. `yaak request schema http`)
  - Deletion requires confirmation (--yes for non-interactive environments)

Overview (`crates-server/yaak-playground/README.md`)

yaak-playground

The API behind yaak.run, for trying Yaak without bringing your own.

  • Users and todos are fixed sample data.
  • Posts can be created, changed, and deleted. Each client IP gets its own copy, which goes back
    to the sample data YAAK_PLAYGROUND_RESET_AFTER_SECS after its first write.
  • POST /auth/token returns a bearer token for GET /auth/me. POST /auth/session sets a
    session cookie for GET /auth/session. Any username with the password yaak logs in.
  • GET / lists every endpoint and GET /openapi.json describes them.

Nothing is written to disk, so a restart resets everything.

sh
cargo run -p yaak-playground   # http://127.0.0.1:9228

Run yaak-playground --help for the limits. Behind a load balancer that sets
X-Forwarded-For, set YAAK_PLAYGROUND_TRUST_FORWARDED_FOR=true.

The image is Dockerfile.playground at the repo root.


Overview (`crates-server/yaak-web/README.md`)

yaak-web

The network half of Yaak in a browser โ€” and, with --serve, the half that
hands the browser the app in the first place.

A tab can't see an HTTP response the way a desktop app can: CORS hides most
headers (2 of 8 in a typical response), redirects are followed silently, and
there is no timeline. So the tab renders the request and posts it here, and this
process puts it on the network with the desktop's own engine (yaak-http) and
streams back everything that happened โ€” every header, every redirect hop, DNS
timing, the body โ€” for the tab to store.

It is a stateless executor. It keeps nothing: no database, no files, no
sessions, no cookies between calls. Every byte it sees comes from the tab in the
request, and every byte it returns is stored by the tab. Restart it any time.

Self-hosting it

One container, no configuration, nothing behind it:

shell
docker run -p 8080:8080 ghcr.io/mountain-loop/yaak-web

Open http://localhost:8080. The image carries the built web client and this
binary, which serves it โ€” so the app and its sends are on one origin, and the
tab's send URL is a path (/v1/http/send) rather than an address anyone has to
configure. The image is linux/amd64 and linux/arm64, built from
Dockerfile.web at the repo root.

Your data lives in your browser (SQLite compiled to wasm, in IndexedDB), not in
the container. The container is stateless: nothing is written to disk, so
upgrading is docker pull and nothing else.

Two settings are worth knowing about:

shell
docker run -p 8080:8080 \
  -e YAAK_WEB_ALLOW_PRIVATE_NETWORKS=true \
  -e YAAK_WEB_RATE_LIMIT_PER_MINUTE=0 \
  ghcr.io/mountain-loop/yaak-web
  • YAAK_WEB_ALLOW_PRIVATE_NETWORKS=true lets sends reach loopback,
    private and link-local addresses. Off by default, and it should stay off on
    anything strangers can reach โ€” see What it refuses.
    Turn it on for an instance on your own network, where calling the API on the
    next machine is the whole point. Note that "private" is relative to the
    container: 127.0.0.1 is the container itself, and reaching the Docker
    host means host.docker.internal (or --network host).
  • YAAK_WEB_RATE_LIMIT_PER_MINUTE defaults to 120 sends per client IP,
    which suits a public instance and not a team of your own; 0 disables it.

Behind a reverse proxy, add YAAK_WEB_TRUST_FORWARDED_FOR=true so the rate
limit sees real client addresses instead of its own โ€” and only then, since
otherwise anyone can spoof the header. If the reverse proxy buffers responses,
tell it not to: sends are streamed, and the X-Accel-Buffering: no header this
binary sets is honoured by nginx-shaped ones.

Running it from source

shell
cargo run -p yaak-web -- --serve dist/apps/yaak-client

after a YAAK_TARGET=web SKIP_WASM_BUILD=1 npx vp -C apps/yaak-client build.
Without --serve it is the send executor alone, which is what the frontend
dev server wants:

shell
cargo run -p yaak-web
YAAK_TARGET=web npm run dev --workspace @yaakapp/yaak-client

The dev server passes /v1 through to this binary, so a dev build sends to its
own origin exactly like a production build does โ€” one address to open, and no
CORS in the loop. PORT moves this server and the dev server follows it. A
production build also sends to its own origin, unless VITE_YAAK_WEB_URL was
set when it was built.

Reaching the dev server from another machine is HOST=0.0.0.0. Vite allows
addresses but not names, so opening it as a hostname also needs
ALLOWED_HOSTS=that-name.

Configuration

Every flag has an environment variable, so a container needs no arguments;
--help lists them all. The listen address uses the platform conventions โ€”
HOST and PORT โ€” so the published image runs unchanged on a
platform-as-a-service that assigns a port. Everything else is YAAK_WEB_*.

Flag Default What
--serve off Also serve a built web client from this directory, on the same origin.
--host 127.0.0.1 Interface to listen on. The image sets 0.0.0.0. May be a name; localhost resolves.
--port 9227 Port to listen on. The image sets 8080.
--allow-private-networks off Allow sends to loopback, private and link-local addresses.
--allowed-origins * CORS origins, comma-separated. Unused when the app is served from here: same origin, no CORS.
--max-request-bytes 16 MiB Largest rendered request accepted from the tab.
--max-response-bytes 64 MiB Largest upstream body relayed before the send is cut off.
--max-timeout-secs 60 Ceiling on a send's timeout; a request asking for more (or none) gets this.
--rate-limit-per-minute 120 Sends per client IP per minute; 0 disables.
--max-concurrent 256 Sends in flight at once.
--trust-forwarded-for off Take the client IP from X-Forwarded-For. Only behind a load balancer that sets it.

Logging

Default logs contain a startup message and operational warnings and errors, not request URLs, client IPs,
DNS lookups, or per-request timing. To diagnose a self-hosted instance, opt in with
RUST_LOG=warn,yaak_http=error,yaak_web=debug. This includes request URLs and IPs,
so only enable it while debugging. Hosting infrastructure may maintain its own
access logs independently.

Serving the app

--serve DIR puts a file server behind the API routes: /v1/* is matched
first, everything else comes from DIR, and a path with no file behind it gets
index.html so the app's own routes survive a refresh. Responses are compressed
(gzip or zstd) on the fly. /assets/* is cached forever โ€” Vite content-hashes
those names โ€” and everything else is no-cache, so a new deploy arrives on the
next reload.

Serving files changes nothing about sending: the same rendered request, the same
destination policy, the same stateless executor. It exists so that a
self-hosted Yaak is one thing to run rather than two.

Split deployments

The app and the sender can still be separate services โ€” one CDN-hosted bundle and
one server elsewhere, or one server shared by several fronts. Then the bundle has
to be told where to send, at build time:

shell
docker build -f Dockerfile.web \
  --build-arg VITE_YAAK_WEB_URL=https://send.example.com .

and the server needs the CORS origins its callers use, since the requests are no
longer same-origin:

shell
docker run -p 8080:8080 \
  -e YAAK_WEB_ALLOWED_ORIGINS=https://yaak.example.com \
  ghcr.io/mountain-loop/yaak-web \
  yaak-web

The trailing yaak-web is a command override: the same image run without
--serve, so it executes sends and serves no app.

What it refuses, and why

A hosted sender is, by construction, a machine that makes HTTP requests on
behalf of strangers. Left alone that is an open relay into whatever network it
sits on. So by default it refuses to connect to:

  • loopback (127/8, ::1), private (10/8, 172.16/12, 192.168/16,
    fc00::/7), link-local (169.254/16 โ€” where cloud metadata lives โ€” and
    fe80::/10), carrier-grade NAT, multicast, reserved and unspecified ranges,
    IPv4 addresses carried inside IPv6 forms (::ffff:a.b.c.d, the well-known
    NAT64 prefix, 6to4), and the whole NAT64 local-use range;
  • anything not http:// or https://.

The check runs on the resolved addresses, after DNS, for every hop of a
redirect chain, so a public hostname that points at an internal address is
caught, and so is a Location: header that points at one. It also refuses body
types that would read files on its own disk (binary, multipart file
fields), since no browser tab could legitimately mean those.

Refusal reasons are included in debug logs when enabled. On a public instance
(web.yaak.app, or anything else strangers can reach), private-network protection
must stay on: the machine's private
network is the host's, not the user's, so a localhost or LAN API is not the
user's to reach through it โ€” the desktop app is what reaches those. On an
instance you run for yourself, that reasoning is inverted, and
--allow-private-networks inverts the policy with it. It allows every range
above, including 169.254.169.254, so use it only where the network on the
other side is one the users are entitled to.

There is no authentication either way: an instance is anonymous, protected by
the per-client rate limit and the destination policy. Anything more (a shared
token, per-user quotas) is a later slice and would sit in front of send_http
in lib.rs, or in middleware on the exported router. Put TLS in front of a public instance.

The wire

POST /v1/http/send with a JSON body:

json
{
  "request":  { "url": "https://โ€ฆ", "method": "GET", "headers": [โ€ฆ], "body": {โ€ฆ}, "bodyType": null, "urlParameters": [โ€ฆ] },
  "settings": { "validateCertificates": true, "followRedirects": true, "timeoutMs": 0, "sendCookies": true, "storeCookies": true },
  "cookies":  [ โ€ฆ ]
}

request is a Yaak HttpRequest in the desktop's own model shape with every
template already rendered by the tab; the server builds the URL, headers and
body from it exactly the way the desktop does after rendering. cookies is the
jar's contents (or null for no jar).

The reply is application/x-ndjson, one JSON frame per line, in the order things
happened:

type When Carries
event as the engine produces them one timeline event, in the desktop's http_response_event.event shape
response once, when the final hop's headers arrive status, all headers, request headers as sent, remote address, HTTP version, timing
body as the body is read a decompressed chunk, base64
done last, on success elapsed, byte counts, and the cookie jar as the send left it
error last, on failure the reason, and any cookies collected before the failure

Refusals that happen before anything is sent (a blocked destination, a bad body,
rate limit, capacity) are plain HTTP errors (403, 400, 429, 503) with
{"error": "โ€ฆ"}, not streams.

Why a streamed HTTP response and not a WebSocket: one POST is stateless by
construction, cancellable by closing the connection, readable with curl, and
needs no upgrade handling on either side. A WebSocket only earns its keep when
traffic is bidirectional, which a single send is not.

The TypeScript side of this contract is generated from src/wire.rs by ts-rs
into bindings/ (run cargo test -p yaak-web after changing a frame)
and published to the tab as @yaakapp-internal/web, so a change to the
wire on one side is a type error on the other.

GET /v1/health reports the version and the effective limits.

What comes later

Not built, by design, but the router is shaped for it: a WebSocket relay
(/v1/ws/relay) and a gRPC relay (/v1/grpc/relay) would be long-lived,
bidirectional endpoints on the same binary, behind the same destination policy
and limits. They differ from this endpoint in holding per-connection
in-memory state while a connection is open (never persisted), which brings
connection limits and a larger abuse surface โ€” the reason they are separate
work.


Overview (`packages/plugin-runtime-types/README.md`)

Yaak Plugin API

Yaak is a desktop API client for
interacting with REST, GraphQL, Server Sent Events (SSE), WebSocket, and gRPC APIs. It's
built using Tauri, Rust, and ReactJS.

Plugins can be created in TypeScript, which are executed alongside Yaak in a NodeJS
runtime. This package contains the TypeScript type definitions required to make building
Yaak plugins a breeze.

Quick Start

The easiest way to get started is by generating a plugin with the Yaak CLI:

shell
npx @yaakapp/cli generate

For more details on creating plugins, check out
the Quick Start Guide

Installation

If you prefer starting from scratch, manually install the types package:

shell
npm install -D @yaakapp/api

Overview (`plugins-external/faker/README.md`)

Yaak Faker Plugin

This is a template function that generates realistic fake data
for testing and development using FakerJS.

Example JSON Body

Here's an example JSON body that uses fake data:

json
{
  "id": "${[ faker.string.uuid() ]}",
  "name": "${[ faker.person.fullName() ]}",
  "email": "${[ faker.internet.email() ]}",
  "phone": "${[ faker.phone.number() ]}",
  "address": {
    "street": "${[ faker.location.streetAddress() ]}",
    "city": "${[ faker.location.city() ]}",
    "country": "${[ faker.location.country() ]}",
    "zipCode": "${[ faker.location.zipCode() ]}"
  },
  "company": "${[ faker.company.name() ]}",
  "website": "${[ faker.internet.url() ]}"
}

This will generate a random JSON body on every request:

json
{
  "id": "589f0aec-7310-4bf2-81c4-0b1bb7f1c3c1",
  "name": "Lucy Gottlieb-Weissnat",
  "email": "[email protected]",
  "phone": "411.805.2871 x699",
  "address": {
    "street": "846 Christ Mills",
    "city": "Spencerfurt",
    "country": "United Kingdom",
    "zipCode": "20354"
  },
  "company": "Emard, Kohler and Rutherford",
  "website": "https://watery-detective.org"
}

Available Categories

The plugin provides access to all FakerJS modules and their methods:

Category Description Example Methods
airline Airline-related data aircraftType, airline, airplane
animal Animal names and types bear, bird, cat, dog, fish
color Colors in various formats human, rgb, hex, hsl
commerce E-commerce data department, product, price
company Company information name, catchPhrase, bs
database Database-related data column, type, collation
date Date and time values recent, future, past, between
finance Financial data account, amount, currency
git Git-related data branch, commitEntry, commitSha
hacker Tech/hacker terminology abbreviation, noun, phrase
image Image URLs and data avatar, url, dataUri
internet Internet-related data email, url, ip, userAgent
location Geographic data city, country, latitude, longitude
lorem Lorem ipsum text word, sentence, paragraph
person Personal information firstName, lastName, fullName
music Music-related data genre, songName, artist
number Numeric data int, float, binary, hex
phone Phone numbers number, imei
science Scientific data chemicalElement, unit
string String utilities uuid, alpha, alphanumeric
system System-related data fileName, mimeType, fileExt
vehicle Vehicle information vehicle, manufacturer, model
word Word generation adjective, adverb, conjunction

Overview (`plugins-external/httpsnippet/README.md`)

Yaak HTTP Snippet Plugin

Generate code snippets for HTTP requests in various languages and frameworks,
powered by @readme/httpsnippet.

How It Works

Right-click any HTTP request (or use the ... menu) and select Generate Code Snippet.
A dialog lets you pick a language and library, with a live preview of the generated code.
Click Copy to Clipboard to copy the snippet. Your language and library selections are
remembered for next time.

Supported Languages

Each language supports one or more libraries, listed in the order they appear in the
dialog:

Language Libraries
Agent Agent Prompt
C Libcurl
Clojure clj-http
Crystal http::client
C# HttpClient, RestSharp
Go NewRequest
HTTP HTTP/1.1
Java AsyncHttp, java.net.http, OkHttp, Unirest
JavaScript XMLHttpRequest, Axios, fetch, jQuery
Kotlin OkHttp
Node.js HTTP, Axios, fetch
Objective-C NSURLSession
OCaml CoHTTP
PHP cURL, Guzzle, HTTP v1, HTTP v2
Powershell Invoke-WebRequest, Invoke-RestMethod
Python Requests
R httr
Ruby net::http, faraday
Rust reqwest
Shell cURL, HTTPie, Wget
Swift URLSession

The Agent target produces a prompt describing the request rather than code, for handing
to an AI coding agent.

Features

  • Renders template variables before generating snippets, so the output reflects real values
  • Supports all body types: JSON, form-urlencoded, multipart, GraphQL, and raw text
  • Includes authentication headers (Basic, Bearer, and API Key)
  • Includes query parameters and custom headers

Overview (`plugins-external/mcp-server/README.md`)

Yaak MCP Server Plugin

Exposes Yaak's functionality via the Model Context Protocol (MCP).

Deprecated. Yaak now has a built-in MCP server with the same tools. Uninstall this plugin,
then turn the server on in Settings > Agents, or run yaak mcp from the Yaak CLI.

Setup

Add this to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):

json
{
  "mcpServers": {
    "yaak": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://127.0.0.1:64343/mcp"]
    }
  }
}

Restart Claude Desktop and make sure Yaak is running.

Available Tools

  • list_http_requests - List all HTTP requests in a workspace
  • get_http_request - Get details of a specific HTTP request
  • send_http_request - Send an HTTP request and get the response
  • create_http_request - Create a new HTTP request
  • update_http_request - Update an existing HTTP request
  • delete_http_request - Delete an HTTP request
  • list_folders - List all folders in a workspace
  • list_workspaces - List all open workspaces
  • get_workspace_id - Get the current workspace ID
  • get_environment_id - Get the current environment ID
  • copy_to_clipboard - Copy text to the system clipboard
  • show_toast - Show a toast notification in Yaak

Environment Selection

send_http_request accepts an optional environmentId for that execution. It overrides the
active environment without changing the selection in Yaak. Omitting it uses the host's
active environment, as before.

The ID must identify a base or sub-environment in the request's workspace. An empty,
unknown, foreign-workspace, or folder environment ID returns an error before sending.
Use the base environment ID to run with global variables only. Folder variables still
apply according to the request's folder hierarchy.

json
{
  "id": "rq_example",
  "workspaceId": "wk_example",
  "environmentId": "ev_staging"
}

This requires a Yaak host with support for the environmentId plugin API field;
older hosts ignore it. get_environment_id reports the active selection, not the
override used by a previous send.


Overview (`plugins/action-copy-curl/README.md`)

Copy as cUrl

A request action plugin for Yaak that converts HTTP requests into curl
commands, making it easy to share, debug, and execute requests outside Yaak.

Overview

This plugin adds a 'Copy as Curl' action to HTTP requests, converting any request into its
equivalent curl command. This is useful for debugging, sharing requests with team members,
and executing requests in terminal environments where curl is available.

How It Works

The plugin analyzes the given HTTP request and generates a properly formatted curl command
that includes:

  • HTTP method (GET, POST, PUT, DELETE, etc.)
  • Request URL with query parameters
  • Headers (including authentication headers)
  • Request body (for POST, PUT, PATCH requests)
  • Authentication credentials

Usage

  1. Configure an HTTP request as usual in Yaak
  2. Right-click on the request in the sidebar
  3. Select 'Copy as Curl'
  4. The command is copied to your clipboard
  5. Share or execute the command

Generated Curl Examples

Simple GET Request

bash
curl -X GET 'https://api.example.com/users' \
  --header 'Accept: application/json'

POST Request with JSON Data

bash
curl -X POST 'https://api.example.com/users' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data '{
    "name": "John Doe",
    "email": "[email protected]"
  }'

Request with Multi-part Form Data

bash
curl -X POST 'yaak.app' \
  --header 'Content-Type: multipart/form-data' \
  --form 'hello=world' \
  --form file=@/path/to/file.json

Request with Authentication

bash
curl -X GET 'https://api.example.com/protected' \
  --user 'username:password'

Overview (`plugins/action-copy-grpcurl/README.md`)

Copy as gRPCurl

An HTTP request action plugin that converts gRPC requests
into gRPCurl commands, enabling easy sharing,
debugging, and execution of gRPC calls outside Yaak.

Overview

This plugin adds a "Copy as gRPCurl" action to gRPC requests, converting any gRPC request
into its equivalent executable command. This is useful for debugging gRPC services,
sharing requests with team members, or executing gRPC calls in terminal environments where
grpcurl is available.

How It Works

The plugin analyzes your gRPC request configuration and generates a properly formatted
grpcurl command that includes:

  • gRPC service and method names
  • Server address and port
  • Request message data (JSON format)
  • Metadata (headers)
  • Authentication credentials
  • Protocol buffer definitions

Usage

  1. Configure a gRPC request as usual in Yaak
  2. Right-click on the request sidebar item
  3. Select "Copy as gRPCurl" from the available actions
  4. The command is copied to your clipboard
  5. Share or execute the command

Generated gRPCurl Examples

Simple Unary Call

bash
grpcurl -plaintext \
  -d '{"name": "John Doe"}' \
  localhost:9090 \
  user.UserService/GetUser

Call with Metadata

bash
grpcurl -plaintext \
  -H "authorization: Bearer my-token" \
  -H "x-api-version: v1" \
  -d '{"user_id": "12345"}' \
  api.example.com:443 \
  user.UserService/GetUserProfile

Call with TLS

bash
grpcurl \
  -d '{"query": "search term"}' \
  secure-api.example.com:443 \
  search.SearchService/Search

Call with Proto Files

bash
grpcurl -import-path /path/to/protos \
  -proto /other/path/to/user.proto \
  -d '{"email": "[email protected]"}' \
  localhost:9090 \
  user.UserService/CreateUser

Overview (`plugins/auth-aws/README.md`)

AWS Signature Version 4 Auth

A plugin for authenticating AWS-compatible requests using the
AWS Signature Version 4 signing process.
This enables secure, signed requests to AWS services (or any S3-compatible APIs like
Cloudflare R2).

Overview

This plugin provides AWS Signature authentication for API requests in Yaak. SigV4 is used
by nearly all AWS APIs to verify the authenticity and integrity of requests using
cryptographic signatures.

With this plugin, you can securely sign requests to AWS services such as S3, STS, Lambda,
API Gateway, DynamoDB, and more. You can also authenticate against S3-compatible services
like Cloudflare R2, MinIO, or Wasabi.

How AWS Signature Version 4 Works

SigV4 signs requests by creating a hash of key request components (method, URL, headers,
and optionally the payload) using your AWS credentials. The resulting HMAC signature is
added in the Authorization header along with credential scope metadata.

Example header:

text
Authorization: AWS4-HMAC-SHA256 Credential=AKIAโ€ฆ/20251011/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=abcdef123456โ€ฆ

Each request must include a timestamp (X-Amz-Date) and may include a session token if
using temporary credentials.

Configuration

The plugin presents the following fields:

  • Access Key ID โ€“ Your AWS access key identifier
  • Secret Access Key โ€“ The secret associated with the access key
  • Session Token (optional) โ€“ Used for temporary or assumed-role credentials (treated as secret)
  • Region โ€“ AWS region (e.g., us-east-1)
  • Service โ€“ AWS service identifier (e.g., sts, s3, execute-api)

Usage

  1. Configure a request, folder, or workspace to use AWS SigV4 Authentication
  2. Enter your AWS credentials and target service/region
  3. The plugin will automatically sign outgoing requests with valid SigV4 headers

Overview (`plugins/auth-basic/README.md`)

Basic Authentication

A simple Basic Authentication plugin that implements HTTP Basic Auth according
to RFC 7617, enabling secure
authentication with username and password credentials.

Overview

This plugin provides HTTP Basic Authentication support for API requests in Yaak. Basic
Auth is one of the most widely supported authentication methods, making it ideal for APIs
that require simple username/password authentication without the complexity of OAuth
flows.

How Basic Authentication Works

Basic Authentication encodes your username and password credentials using Base64 encoding
and sends them in the Authorization header with each request. The format is:

text
Authorization: Basic <base64-encoded-credentials>

Where `` is the Base64 encoding of username:password.

Configuration

The plugin presents two fields:

  • Username: Username or user identifier
  • Password: Password or authentication token

Usage

  1. Configure the request, folder, or workspace to use Basic Authentication
  2. Enter your username and password in the authentication configuration
  3. The plugin will automatically add the proper Authorization header to your requests

Troubleshooting

  • 401 Unauthorized: Verify your username and password are correct
  • 403 Forbidden: Check if your account has the necessary permissions
  • Connection Issues: Ensure you're using HTTPS for secure transmission

Overview (`plugins/auth-bearer/README.md`)

Bearer Token Authentication Plugin

A Bearer Token authentication plugin for Yaak that
implements RFC 6750, enabling secure API
access using tokens, API keys, and other bearer credentials.

Overview

This plugin provides Bearer Token authentication support for your API requests in Yaak.
Bearer Token authentication is widely used in modern APIs, especially those following REST
principles and OAuth 2.0 standards. It's the preferred method for APIs that issue access
tokens, API keys, or other bearer credentials.

How Bearer Token Authentication Works

Bearer Token authentication sends your token in the Authorization header with each
request using the Bearer scheme:

text
Authorization: Bearer <your-token>

The token is transmitted as-is without any additional encoding, making it simple and
efficient for API authentication.

Configuration

The plugin requires only one field:

  • Token: Your bearer token, access token, API key, or other credential
  • Prefix: The prefix to use for the Authorization header, which will be of the
    format " "

Usage

  1. Configure the request, folder, or workspace to use Bearer Authentication
  2. Enter the token and optional prefix in the authentication configuration
  3. The plugin will automatically add the proper Authorization header to your requests

Troubleshooting

  • 401 Unauthorized: Verify your token is valid and not expired
  • 403 Forbidden: Check if your token has the necessary permissions/scopes
  • Invalid Token Format: Ensure you're using the complete token without truncation
  • Token Expiration: Refresh or regenerate expired tokens

Overview (`plugins/auth-digest/README.md`)

Digest Authentication

An HTTP Digest Authentication plugin that implements
RFC 7616, with fallback to the
older RFC 2617 and
RFC 2069 behaviour that many
servers still speak.

Overview

Digest Authentication proves you know a password without ever putting it on the
wire. The server issues a one-time nonce, and the client answers with a hash
over the credentials, the nonce and the request itself.

How it works

Because the digest is computed over a server-issued nonce, the challenge has to
be fetched before the real request can be signed. On each send, the plugin:

  1. Sends an unauthenticated probe of the same method and URL, carrying no
    headers of its own so it cannot authorize anything
  2. Reads the WWW-Authenticate: Digest โ€ฆ challenge from the 401 response
  3. Computes the response hash and returns the Authorization header

Configuration

  • Username: Username or user identifier
  • Password: Password or authentication token
  • Realm (advanced): Only needed when the server offers more than one realm.
    Leave it empty and the first challenge the server prefers is used.

Supported challenges

Feature Supported
Algorithm MD5, MD5-sess, SHA-256, SHA-256-sess
qop auth, auth-int, and challenges that omit qop (RFC 2069)
opaque Echoed back when the server sends one
userhash Declined with userhash=false

auth-int is used when the server offers it and Yaak has the request body in
hand. Bodies that are streamed from disk or above the size Yaak passes to auth
plugins aren't available to hash, so those requests use auth instead.

Credentials are normalized to Unicode NFC before hashing, per RFC 7616 ยง4.
Usernames outside ASCII are sent as an RFC 5987 extended value (username*).

Troubleshooting

  • Server did not offer Digest authentication: The endpoint answered the probe
    with a different scheme, or with no WWW-Authenticate header at all. Check the
    URL, and whether the endpoint requires auth in the first place.
  • Unsupported Digest algorithm: The server asked for an algorithm this plugin
    doesn't implement, such as SHA-512-256.
  • 401 Unauthorized: Verify the username and password. If the server offers
    several realms, set the Realm field to the one your account belongs to.

Overview (`plugins/auth-jwt/README.md`)

JSON Web Token (JWT) Authentication

A JSON Web Token (JWT) authentication
plugin that supports token generation, signing, and automatic header management.

Overview

This plugin provides JWT authentication support for API requests. JWT is a compact,
URL-safe means of representing claims between two parties, commonly used for
authentication and information exchange in modern web applications and APIs.

How JWT Authentication Works

JWT authentication involves creating a signed token containing claims about the user or
application. The token is sent in the Authorization header:

text
Authorization: Bearer <jwt-token>

A JWT consists of three parts separated by dots:

  • Header: Contains the token type and signing algorithm
  • Payload: Contains the claims (user data, permissions, expiration, etc.)
  • Signature: Ensures the token hasn't been tampered with

Usage

  1. Configure the request, folder, or workspace to use JWT Authentication
  2. Set up your signing algorithm and secret/key
  3. Add custom JWT header fields if needed
  4. Configure the required claims for your JWT payload
  5. The plugin will generate, sign, and include the JWT in your requests

Common Use Cases

JWT authentication is commonly used for:

  • Microservices Authentication: Service-to-service communication
  • API Gateway Integration: Authenticating with API gateways
  • Single Sign-On (SSO): Sharing authentication across applications
  • Stateless Authentication: No server-side session storage required
  • Mobile App APIs: Secure authentication for mobile applications
  • Third-party Integrations: Authenticating with external services

Troubleshooting

  • Invalid Signature: Check your secret/key and algorithm configuration
  • Token Expired: Verify expiration time settings
  • Invalid Claims: Ensure required claims are properly configured
  • Algorithm Mismatch: Verify the algorithm matches what the API expects
  • Key Format Issues: Ensure RSA keys are in the correct PEM format

Overview (`plugins/auth-oauth2/README.md`)

OAuth 2.0 Authentication

An OAuth 2.0 authentication plugin that
supports multiple grant types and flows, enabling secure API authentication with OAuth 2.0
providers.

Overview

This plugin implements OAuth 2.0 authentication for requests, supporting the most common
OAuth 2.0 grant types used in modern API integrations. It handles token management,
automatic refresh, and PKCE (Proof Key
for Code Exchange) for enhanced security.

Supported Grant Types

Authorization Code Flow

The most secure and commonly used OAuth 2.0 flow for web applications.

  • Standard Authorization Code flow
  • Optional PKCE (Proof Key for Code Exchange) for enhanced security
  • Supports automatic token refresh

Client Credentials Flow

Ideal for server-to-server authentication where no user interaction is required.

Implicit Flow

Legacy flow for single-page applications (deprecated but still supported):

  • Direct access token retrieval
  • No refresh token support
  • Suitable for legacy integrations

Resource Owner Password Credentials Flow

Direct username/password authentication.

  • User credentials are exchanged directly for tokens
  • Should only be used with trusted applications
  • Supports automatic token refresh

Features

  • Automatic Token Management: Handles token storage, expiration, and refresh
    automatically
  • PKCE Support: Enhanced security for Authorization Code flow
  • Token Persistence: Stores tokens between sessions
  • Flexible Configuration: Supports custom authorization and token endpoints
  • Scope Management: Configure required OAuth scopes for your API
  • Custom Parameters: Add headers and params to the authorization, token, and refresh
    requests, replacing generated entries of the same name
  • Error Handling: Comprehensive error handling and user feedback

Usage

  1. Configure the request, folder, or workspace to use OAuth 2.0 Authentication
  2. Select the appropriate grant type for your use case
  3. Fill in the required OAuth 2.0 parameters from your API provider
  4. The plugin will handle the authentication flow and token management automatically

Compatibility

This plugin is compatible with OAuth 2.0 providers including:

  • Google APIs
  • Microsoft Graph
  • GitHub API
  • Auth0
  • Okta
  • And many other OAuth 2.0 compliant services

Overview (`plugins/importer-bruno/README.md`)

Bruno importer

Import Bruno's Single File (YAML) or Bruno Collection (ZIP) exports, or select a collection directory. Both OpenCollection 1.0.0 YAML collections and legacy .bru collections are supported. Individual .bru request files also work. A directory/ZIP must contain one collection root (opencollection.yml, opencollection.yaml, or bruno.json); a wrapper directory in the ZIP is allowed. Use Choose folder for an on-disk collection, rather than selecting its configuration file alone.

The importer consumes the shared file-tree API. It skips .git, node_modules, __MACOSX, and hidden entries. It does not install dependencies, load dotenv files, or run collection scripts.

Supported:

  • Nested folders, ordering, descriptions, collection/folder headers, and variables.
  • Environments, disabled variables, typed values, and secret placeholders. Environment inheritance is flattened into each selectable environment because Yaak does not have nested selectable environments.
  • HTTP URLs, query/path parameters, headers, raw bodies, URL-encoded forms, multipart files, and selected binary files.
  • GraphQL query and variables in Yaak's sendable body shape.
  • gRPC URL, service/method, metadata, and selected message; WebSocket URL, headers, and selected message.
  • Explicit auth inheritance, no auth, basic, bearer, digest, NTLM, API key, AWS credentials, common OAuth 1.0 header signing, and OAuth 2.0 flows with header tokens.
  • Plain {{variable}} references converted to Yaak templates. Expressions, dynamic variables, and process.env references stay unchanged.

Scripts, assertions, actions, request-scoped variables, examples, advanced settings, and unsupported authentication configurations are retained in resource descriptions for manual migration, not executed. Alternative bodies/messages use the selected variant (or the first when unspecified); retained source variants remain in descriptions. File paths are preserved without reading files; relative paths and gRPC proto files may need to be configured after import. External secret providers, certificates, proxies, OAuth token caches, and WebSocket binary messages are not activated by the importer.

OpenCollection does not export persistent resource IDs, so Yaak derives the import source keys using its existing fallback matching.

Implementation references:

Legacy .bru parsing uses Bruno's official @usebruno/filestore (backed by @usebruno/lang). Its YAML serializers normalize parsed collections to OpenCollection before they enter the same Yaak conversion path. YAML collections are assembled directly to preserve their fields.


Overview (`crates/common/yaak-rpc-schema/README.md`)

yaak-rpc-schema

The wire schema for the app's RPC surface: every command name, its request
payload, and its response type, declared once.

Every host that serves the Yaak UI โ€” the desktop app today, the browser bridge
and anything after it โ€” imports these types and implements the commands against
them. That is what keeps a request's shape from drifting between hosts, and it
is why the TypeScript bindings (bindings/gen_rpc.ts, exposed to the frontend
as @yaakapp-internal/rpc-schema) are generated from one place.

Nothing here depends on Tauri or on any host. Request structs are plain data,
and so are the few response types declared here rather than in an engine crate.
Command bodies live with the host that runs them.

Adding a command

  1. Add its request struct and an entry in with_commands! in src/lib.rs.
  2. Write the adapter in each host โ€” the desktop's live in
    crates-tauri/yaak-app-client/src/rpc_ext.rs. A host that does not support
    the command still has to say so; a missing adapter fails to compile.
  3. Regenerate the bindings: cargo test -p yaak-rpc-schema writes
    bindings/gen_rpc.ts, which is committed.

How hosts consume the list

with_commands! takes the name of a macro_rules! macro and calls it with the
full name(Req) -> Res list. Each host writes a small macro that receives that
list and builds its router:

rust
macro_rules! register_commands {
    ( $( $name:ident ( $req:ty ) -> $res:ty ),* $(,)? ) => {
        pub fn build_router() -> RpcRouter<MyCtx> {
            let mut router = RpcRouter::new();
            $( router.register(stringify!($name), rpc_handler_async!($name)); )*
            router
        }
    };
}
yaak_rpc_schema::with_commands!(register_commands);

The schema decides what commands exist; the host decides how each one runs.


Overview (`packages/platform/src/web/README.md`)

The browser host

Yaak running in a plain tab: no install, no local process. The desktop's own
model layer โ€” yaak-models, SQLite included โ€” runs compiled to wasm inside a
worker the tab talks to, so a browser stores exactly what a desktop install
stores, migrations and all.

Select it at build time. vp run web:dev does that and starts the send server
alongside the frontend:

shell
vp run web:dev

The flag resolves @yaakapp-internal/platform to ../index.web.ts, which
installs this host instead of the Tauri one. It is a separate entry rather than
a branch inside index.ts so that a web build never pulls @tauri-apps/* into
the module graph at all โ€” a folded branch would drop the code but keep the
imports it guarded.

Desktop builds are untouched: without the flag, packages/platform/src/index.ts
installs the Tauri host exactly as before.

How it fits together

text
tab (index.ts, commands.ts) โ”€โ”€MessagePortโ”€โ”€โ–ถ worker.ts โ”€โ”€โ–ถ @yaakapp-internal/web (wasm)
        โ”‚                   โ—€โ”€โ”€ model_writes โ”€โ”€          crates/yaak-wasm โ†’ yaak-models โ†’ SQLite
        โ”‚                                                               โ””โ”€ pages in IndexedDB
        โ””โ”€โ”€ send.ts โ”€โ”€POST rendered requestโ”€โ”€โ–ถ yaak-web (crates-server) โ”€โ”€โ–ถ the internet
                     โ—€โ”€โ”€ NDJSON: events, response, body, cookies โ”€โ”€
File What it is
index.ts The Platform implementation.
commands.ts The command table: model commands forward to the worker; the rest is fixed answers and refusals-with-a-reason.
connection.ts A tab's end of the wire: request/response over a MessagePort, event delivery, and the tab's identity (label).
send.ts Sending: the worker renders (prepare_http_send), the server executes, this file stores what comes back where the desktop stores it.
server.ts Where the Yaak server is, and the wire shapes it speaks (generated from crates-server/yaak-web/src/wire.rs).
worker.ts The process that owns the database. Loads the wasm, opens the DB once, answers each port, fans model_writes out to every port.
protocol.ts The message shapes both sides import.
errors.ts UnsupportedCommandError, the structured refusal.
storage.ts navigator.storage.persist().

The Rust side is crates/yaak-wasm (@yaakapp-internal/wasm): boot(),
rpc(cmd, payload, label) returning { result, events }, blob get/put, and
prepare_http_send(payload) โ€” the database half of a send (environment chain,
inherited headers and auth, request settings, cookie jar, rendering), which is
yaak_models::render::render_http_request, the same function the desktop
renders with.
Its pkg/ is committed; rebuilding needs a clang with a WebAssembly backend
(brew install llvm), and build-wasm.cjs skips with a notice when there
isn't one, so a desktop npm run bootstrap never depends on it.

Behaviours worth knowing before changing anything:

  • The worker is a SharedWorker, and only that. The browser hands every
    tab on the origin the same one, which is what makes "one database owner"
    true without anyone coordinating โ€” and makes the browser look like the
    desktop: one process holds the data, every window talks to it, it pushes
    writes to all of them. It still takes a Web Lock before opening, for the one
    overlap the browser doesn't rule out (a reloading tab's dying predecessor).
    There is deliberately no fallback to a per-tab worker: two kinds of worker
    that can both come up is a race. Every current browser, desktop and mobile,
    has SharedWorker and Web Locks (Chrome for Android since 148, April 2026);
    older ones get a clear "unsupported browser" message rather than a second
    SQLite over the same pages.
  • Every write is stamped with the calling tab's label as
    UpdateSource::Window, exactly like a desktop window label, so the frontend
    store's echo handling is unchanged.
  • Cascade rules, duplicate naming, id generation, serde defaults, and the
    lazy first-run bootstrap are all the Rust code's.
    Nothing about what a
    model is is decided in TypeScript.
  • Persistence is relaxed-idb: SQLite pages live in IndexedDB, writes land
    in memory and flush shortly after. A tab closing mid-flush loses at most the
    last few writes.

Commands

109 commands are declared in @yaakapp-internal/rpc-schema. This host answers
32, declines 43 by name with a reason, and refuses the remaining 34 generically.

Implemented (32)

Group Commands
Models models_workspace_models, models_upsert, models_delete, models_duplicate, models_get_settings, models_get_graphql_introspection, models_upsert_graphql_introspection, models_grpc_events, models_websocket_events
Sending cmd_send_http_request (through the Yaak server; see below)
App cmd_metadata, cmd_get_workspace_meta, cmd_default_headers, cmd_get_themes, cmd_check_for_updates, cmd_dismiss_notification, cmd_plugin_init_errors
Bodies cmd_http_response_body, cmd_http_response_body_path, cmd_http_request_body, cmd_get_http_response_events, cmd_get_sse_events
Plugin surfaces (empty results) cmd_http_request_actions, cmd_websocket_request_actions, cmd_grpc_request_actions, cmd_workspace_actions, cmd_folder_actions, cmd_template_function_summaries, cmd_get_http_authentication_summaries, cmd_get_http_authentication_config
Text cmd_format_json, cmd_render_template

Some of these answer honestly rather than fully, and the difference matters:

  • cmd_render_template returns the template unrendered. Resolving variables
    and calling template functions is plugin work. The preview shows the raw
    ${[โ€ฆ]} rather than a wrong value.
  • cmd_get_http_authentication_summaries returns the auth methods Yaak ships as
    plugins, so the picker is truthful about the product โ€” but
    cmd_get_http_authentication_config returns an empty form, because the plugin
    that defines the form isn't running.
  • cmd_template_function_summaries returns one provider contributing no
    functions. Both summary commands are polled every second until they return
    something, so an empty list is a poll that never stops rather than a quiet no.
  • cmd_metadata reports empty strings for the data, log, plugin and project
    directories. There is no filesystem behind this host.

Declined by name (43)

Each returns an UnsupportedCommandError carrying cmd, a user-facing
message, and the capability a caller should have checked. The UI turns it
into a toast.

Reason Commands
Sending, the parts not wired yet cmd_send_ephemeral_request, cmd_delete_send_history, cmd_delete_all_http_responses, cmd_import_url
No plugin runtime cmd_reload_plugins, cmd_plugin_info, cmd_plugins_search, cmd_plugins_install, cmd_plugins_install_from_directory, cmd_plugins_uninstall, cmd_plugins_updates, cmd_plugins_update_all, cmd_template_function_config, cmd_template_tokens_to_string, cmd_call_http_request_action, cmd_call_websocket_request_action, cmd_call_grpc_request_action, cmd_call_workspace_action, cmd_call_folder_action, cmd_call_http_authentication_action, cmd_curl_to_request, cmd_format_graphql
No filesystem cmd_import_data, cmd_export_data, cmd_save_response, cmd_save_base64_to_binary
Needs a real socket cmd_grpc_reflect, cmd_grpc_go, cmd_delete_all_grpc_connections, cmd_ws_connect, cmd_ws_send, cmd_ws_close, cmd_ws_delete_connections
Workspace encryption cmd_enable_encryption, cmd_disable_encryption, cmd_reveal_workspace_key, cmd_set_workspace_key, cmd_secure_template, cmd_decrypt_template
One tab, no windows cmd_new_child_window, cmd_new_main_window, cmd_restart
Other cmd_send_feedback

Refused generically (34)

The 30 cmd_git_* commands and cmd_sync_calculate, cmd_sync_calculate_fs,
cmd_sync_apply, cmd_sync_watch. Nothing in the app reaches them unless a
workspace has a sync directory, which a browser tab cannot set.

Anything added to the schema later also lands here, and the error names the
command โ€” an unlisted command is a gap in commands.ts, and whoever hits it
should be able to see which.

Capabilities

Reported honestly, so callers gate on the question rather than on the host:

True False
httpSending, timeline, cookieJar grpc, websocket, git, sync, tlsOptions, localFiles, multiWindow, windowChrome, interfaceZoom, plugins, encryption, updater, clipboardRead, systemFonts, license

interfaceZoom: false leaves Cmd/Ctrl +, - and 0 to the browser instead
of swallowing them, and drops those three rows from the hotkeys screen.

multiWindow: false means the host cannot open a second window on demand โ€”
what cmd_new_child_window does for Settings and workspace switching. It is not
a claim that nothing else is looking: other tabs may well be open on the same
worker, and it pushes every write to all of them regardless.

windowChrome: false means the browser owns the frame around the page, so the
header draws no window controls and reserves no room for macOS traffic lights.

Multiple tabs

Each tab mints a label at load (tab_xxxxxxxx) and sends it with every command;
the worker stamps writes with it as UpdateSource::Window { label }, standing in
for the desktop's window label. The worker fans each write out to every
connected tab, and the receiving tab's store applies or ignores it exactly as a
desktop window would.

The label is deliberately not kept in sessionStorage: duplicating a tab
copies session storage, and two tabs sharing one identity would each mistake the
other's writes for an echo of their own and drop them.

Known gaps

  • Storage persistence is requested, not guaranteed. navigator.storage.persist()
    runs at boot; browsers grant it on their own heuristics and often decline on
    localhost.
  • pkg/yaak_web_bg.wasm is 3.8 MB and committed (no wasm-opt, matching
    yaak-templates). It will churn on every model-layer change; a CI-built
    artifact is the real answer.
  • Settings opens in the same tab and is left with the browser's Back button.
  • Settings shows Data Directory / Logs Directory rows with empty values; the
    Create Workspace dialog offers directory sync and encryption. Should be gated
    on localFiles / sync / encryption.
  • cmd_render_template returns the template unrendered. Resolving variables
    and calling template functions is plugin work.
  • A declined command logs an unhandled rejection next to its toast โ€” the
    app's own createFastMutation.mutate, same on desktop.
  • yaak-rpc-schema does not come to wasm (it pulls the git/gRPC/plugin
    crates for their types), so the crate declares the handful of request shapes
    it needs locally, and commands.ts stays typed against RpcSchema.

Sending

A page cannot see a response the way a desktop app can โ€” CORS exposes a handful
of headers, redirects are followed silently, there is no timeline โ€” so the
network half of a send runs on a small stateless server,
crates-server/yaak-web. This layer stays the only place data lives:

  1. send.ts creates the http_response row (state initialized), as the
    desktop does, so anything that goes wrong lands in the response pane.
  2. The worker resolves and renders the request (prepare_http_send): the
    environment chain, inherited headers and auth, request settings, the cookie
    jar. This is the desktop's HttpSendInputs, in Rust, on the same model layer,
    with yaak_models::render::render_http_request. Variables (${[ name ]})
    render here with no plugins involved.
  3. The rendered request, the settings and the jar's cookies are POSTed to the
    server. It streams back timeline events, the response head, body chunks and a
    terminal frame carrying the jar as the send left it.
  4. Each frame is written where the desktop writes it: the response row as it
    progresses, http_response_event rows for the timeline (which is why
    timeline is true), the body under the response id via blob_put, and the
    cookie jar through models_upsert. Every write fans out to every tab.

What sends today: any saved request whose templates are variables and whose
authentication is none, or an inline header. Sending a request that needs a
template function (${[ timestamp() ]}) or an authentication plugin (bearer,
basic, OAuth, โ€ฆ) is refused before anything leaves the tab, with a message naming
what it needs; those light up when plugins run in the browser. Requests with a
file body or multipart file fields are refused by the server (it has no access to
your files, and must not read its own). And on a public instance a request to
localhost or a LAN address can't work: the server runs elsewhere and refuses
private ranges outright โ€” that is what the desktop app is for. A self-hosted
server on your own network can be started with --allow-private-networks, which
is the one case where those addresses are the user's to reach.

Where the tab sends (server.ts): its own origin, in development as well as
production, so there is no address to learn and no CORS in the loop. A
production build is served by the sender itself (yaak-web --serve dist/apps/yaak-client, which is what the ghcr.io/mountain-loop/yaak-web
image runs). In development the Vite server passes /v1 through to yaak-web
instead, following PORT to find it โ€” vp run web:dev starts both.
VITE_YAAK_WEB_URL overrides this, for a deployment that keeps the app and the
server apart.


--- METRICS ---