Collections are plain YAML files at the root of your project folder. They are committed to
your repository, so the format is a real contract: it is versioned, output is written
minimally and in a stable order, and only the fields you actually set are emitted.
This page documents schema v0.8.0 , the version shipped in desktop v0.1.0-preview.9.
formatVersion for a collection is 2.
formatVersion for a chain is 3 — chains version independently and live in their own
files under .tinspec/chains/.
url : "{{baseUrl}}/users/{{id}}"
gatewayUrl : "{{gatewayUrl}}"
location : http://localhost:8080/openapi.json
baseUrl : http://localhost:8080
gatewayUrl : https://gw.example.com
The complete set of top-level keys.
Merged into every request in the collection; a request’s own value wins.
Field Type Required headersmap<string, string> no querymap<string, string> no variablesmap<string, string> no — layered under the active environment authAuthRefno — used when a request specifies none
Field Type Required Notes namestring yes protocolhttp | grpc | graphql | websocket | langgraphno Absent means HTTP methodGET | HEAD | POST | PUT | PATCH | DELETE | OPTIONSyes Required even for gRPC and GraphQL — use POST urlstring yes {{var}} allowed; grpc:// or grpcs:// for gRPCquerymap<string, string> no Appended at send time; kept out of the URL string headersmap<string, string> no HTTP headers, or gRPC call metadata bodyBodyno authAuthRefno A reference by provider name — never an inline credential grpcGrpcCallno When protocol: grpc graphqlGraphqlCallno When protocol: graphql langgraphLangGraphCallno When protocol: langgraph prerunChainstring no Name of a chain to run first; its outputs merge into the environment routeMatcherRouteMatcherRefno useCookieJarboolean no Absent or true means the shared jar applies; false opts out testsAssertion []no Evaluated against this request’s own response
Field Type Required Notes contentTypestring yes Advisory for multipart — the client sets the real boundary contentstring yes Use "" for multipart and file bodies kindtext | multipart | fileno Absent means text partsBodyPart []no When kind: multipart filePathstring no When kind: file. Workspace-relative inside the project, absolute otherwise; {{var}} allowed
Field Type Required Notes namestring yes kindtext | fileno Absent means text valuestring no Text part; {{var}} allowed filePathstring no File part contentTypestring no Default: none for text, application/octet-stream for file fileNamestring no Content-Disposition filename; defaults to the path’s basename
Field Type Required Notes protoPathstring no Workspace-relative .proto or descriptor set servicestring yes Fully qualified, e.g. acme.users.v1.UserService methodstring yes messagestring no Request message as JSON; {{var}} allowed messagesstring[] no Further messages for client-streaming and bidi, sent in order after message
Streaming mode is not stored — it is read from the descriptor.
Field Type Required querystring yes variablesstring (JSON-encoded object) no operationNamestring no
Field Type Required Notes inputstring no JSON-encoded payload streamModestream | bufferedno Absent means stream eventsVersionstring no customFields{ name, path }[]no Both required; path is a dot-path into the raw event’s data. A missing path renders blank
Field Type Required namestring yes variablesmap<string, string> no
Secret values never go here.
Used by both Request.tests and a chain’s assert node.
Field Type Required Notes sourcestatus | body | header | varyes pathstring no Dotted JSON path for body, header name for header, variable name for var opeq | ne | contains | gt | lt | exists | matchesyes expectedstring no Unused for exists; {{var}} allowed
Four sources, seven operators. That is the whole vocabulary at v0.8.0.
What a request may say about auth. There is no way to express a credential value here.
Field Type Required providerstring yes — the AuthProvider namesenderstring no — which sender to authenticate as
Field Type Required Notes namestring yes kindscript | integration | requestyes scriptstring no kind: script — workspace-relative path to the JSintegrationkeycloak | clerk | auth0 | firebase | supabaseno kind: integrationrequestRequestno kind: request — any protocolextractExtraction (singular)no kind: request — the result is exposed as {{token}}configmap<string, string> no Non-secret only : baseUrl, realm, clientId, tokenUrl, scope, domain, grant…secretRefsstring[] no Keychain key names , never values sendersAuthSender []no Empty means one implicit identity injectAuthInjectno Default when absent: Authorization: Bearer {{token}}
keycloak and auth0 take config.grant of password or client_credentials. firebase
and supabase exchange email and password for a token. Generic OAuth2 is not an
integration at this version — model it with kind: request or kind: script.
Field Type Required Notes idstring yes labelstring no tenantstring no An informational label only, not a selection axis secretRefstring no This identity’s keychain key name varsmap<string, string> no Non-secret per-sender values, available as {{var}} while resolving the token
Field Type Required targetheader | query | cookieyes namestring yes templatestring yes — e.g. Bearer {{token}}, or bare {{token}}
Field Type Required namestring yes locationstring yes — URL or file path to the OpenAPI documentrouteMatcherRouteMatcherRefno
Field Type Required Notes namestring yes kindkong | nginx | envoyyes configstring yes The raw gateway config, stored inline overrides{ upstream, from, to }[]no All three required. from is the backend path, to the gateway-facing one transcodesboolean no gRPC↔JSON transcoding; auto-detected for Envoy
Field Type Required Notes matcherstring yes upstreamstring no Omit to auto-match the spec’s server URL against route upstreams gatewayUrlstring no scheme://host[:port]; {{var}} allowed
The original linear chaining path. It is still in the format, but
chains are the chaining surface in the app.
Type Field Required Pipelinenameyes stepsno PipelineSteprequest — a Request name in the same collectionyes extractno
Field Type Required Notes varstring yes frombody | header | statusyes pathstring no Dotted JSON path for body, header name for header, unused for status
A separate file — .tinspec/chains/<name>.yaml, formatVersion: 3.
Field Type Required formatVersionnumber yes namestring yes nodesChainNode[]yes edgesChainEdge[]yes
Field Type Required Applies to idstring yes all — edges reference it typesee below yes all position{ x, y }, both requiredyes all requestChainRequestRefno requestextractExtraction []no requestutility{ fn, var, args? }no generatorrules{ expr?, port, label? }[]no router — ordered, first match wins; an empty expr always matchesconfigmap<string, string> no Per-kind settings scriptstring no script — async function run(vars, ctx); returned keys become outputsassertionsAssertion []no assertfields{ var, value }[]no setportsNodePortsno all
type is one of the sixteen kinds: request, input, environment, math,
transform, generator, set, aggregate, script, assert, condition, router,
loop, merge, split, delay.
config keys by kind: input → value; environment → key; math → expr, var;
transform → op, expr, var; condition → expr; delay → ms; loop → mode,
count, listVar, itemVar, indexVar; aggregate → fromVar, intoVar.
ChainRequestRef is either collection (workspace-relative path) + name, or inline
(a whole Request ). If both are given, inline wins.
utility.fn is uuid, randomInt (min/max), randomString (length), timestamp
(format: unix or iso), sequence (start/step), or list (values, comma-separated).
NodePorts : inputEnabled, outputEnabled (both default true), inputs / outputs as
{ var, from } mappings (empty means pass everything through), retry, retryDelayMs.
Field Type Required Notes idstring yes sourcestring yes targetstring yes sourceHandlestring no true/false for a condition, a rule port for a router, body/done for a loop
The writer emits only what is set, in a stable order. An empty tests list is dropped rather
than written as tests: []; useCookieJar: true is stored as absent because it is the
default. A one-header change should be a one-line diff — if it is not, that is a bug worth
reporting.