Skip to content

Collection file format

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/.
formatVersion: 2
name: Example API
requests:
- name: Get user
protocol: http
method: GET
url: "{{baseUrl}}/users/{{id}}"
headers:
Accept: application/json
auth:
provider: keycloak-dev
routeMatcher:
matcher: edge-kong
upstream: users-service
gatewayUrl: "{{gatewayUrl}}"
specSources:
- name: Users API
location: http://localhost:8080/openapi.json
routeMatchers:
- name: edge-kong
kind: kong
config: |
services:
- name: users-service
url: http://users:8000
routes:
- name: users
paths: [/api/v1]
strip_path: true
environments:
- name: local
variables:
baseUrl: http://localhost:8080
gatewayUrl: https://gw.example.com
id: "1"

The complete set of top-level keys.

KeyTypeRequired
formatVersionnumberyes
namestringyes
requestsRequest[]no
environmentsEnvironment[]no
pipelinesPipeline[]no
specSourcesSpecSource[]no
authProvidersAuthProvider[]no
routeMatchersRouteMatcher[]no
defaultsRequestDefaultsno

Merged into every request in the collection; a request’s own value wins.

FieldTypeRequired
headersmap<string, string>no
querymap<string, string>no
variablesmap<string, string>no — layered under the active environment
authAuthRefno — used when a request specifies none
FieldTypeRequiredNotes
namestringyes
protocolhttp | grpc | graphql | websocket | langgraphnoAbsent means HTTP
methodGET | HEAD | POST | PUT | PATCH | DELETE | OPTIONSyesRequired even for gRPC and GraphQL — use POST
urlstringyes{{var}} allowed; grpc:// or grpcs:// for gRPC
querymap<string, string>noAppended at send time; kept out of the URL string
headersmap<string, string>noHTTP headers, or gRPC call metadata
bodyBodyno
authAuthRefnoA reference by provider name — never an inline credential
grpcGrpcCallnoWhen protocol: grpc
graphqlGraphqlCallnoWhen protocol: graphql
langgraphLangGraphCallnoWhen protocol: langgraph
prerunChainstringnoName of a chain to run first; its outputs merge into the environment
routeMatcherRouteMatcherRefno
useCookieJarbooleannoAbsent or true means the shared jar applies; false opts out
testsAssertion[]noEvaluated against this request’s own response
FieldTypeRequiredNotes
contentTypestringyesAdvisory for multipart — the client sets the real boundary
contentstringyesUse "" for multipart and file bodies
kindtext | multipart | filenoAbsent means text
partsBodyPart[]noWhen kind: multipart
filePathstringnoWhen kind: file. Workspace-relative inside the project, absolute otherwise; {{var}} allowed
FieldTypeRequiredNotes
namestringyes
kindtext | filenoAbsent means text
valuestringnoText part; {{var}} allowed
filePathstringnoFile part
contentTypestringnoDefault: none for text, application/octet-stream for file
fileNamestringnoContent-Disposition filename; defaults to the path’s basename
FieldTypeRequiredNotes
protoPathstringnoWorkspace-relative .proto or descriptor set
servicestringyesFully qualified, e.g. acme.users.v1.UserService
methodstringyes
messagestringnoRequest message as JSON; {{var}} allowed
messagesstring[]noFurther messages for client-streaming and bidi, sent in order after message

Streaming mode is not stored — it is read from the descriptor.

FieldTypeRequired
querystringyes
variablesstring (JSON-encoded object)no
operationNamestringno
FieldTypeRequiredNotes
inputstringnoJSON-encoded payload
streamModestream | bufferednoAbsent means stream
eventsVersionstringno
customFields{ name, path }[]noBoth required; path is a dot-path into the raw event’s data. A missing path renders blank
FieldTypeRequired
namestringyes
variablesmap<string, string>no

Secret values never go here.

Used by both Request.tests and a chain’s assert node.

FieldTypeRequiredNotes
sourcestatus | body | header | varyes
pathstringnoDotted JSON path for body, header name for header, variable name for var
opeq | ne | contains | gt | lt | exists | matchesyes
expectedstringnoUnused 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.

FieldTypeRequired
providerstringyes — the AuthProvider name
senderstringno — which sender to authenticate as
FieldTypeRequiredNotes
namestringyes
kindscript | integration | requestyes
scriptstringnokind: script — workspace-relative path to the JS
integrationkeycloak | clerk | auth0 | firebase | supabasenokind: integration
requestRequestnokind: request — any protocol
extractExtraction (singular)nokind: request — the result is exposed as {{token}}
configmap<string, string>noNon-secret only: baseUrl, realm, clientId, tokenUrl, scope, domain, grant…
secretRefsstring[]noKeychain key names, never values
sendersAuthSender[]noEmpty means one implicit identity
injectAuthInjectnoDefault 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.

FieldTypeRequiredNotes
idstringyes
labelstringno
tenantstringnoAn informational label only, not a selection axis
secretRefstringnoThis identity’s keychain key name
varsmap<string, string>noNon-secret per-sender values, available as {{var}} while resolving the token
FieldTypeRequired
targetheader | query | cookieyes
namestringyes
templatestringyes — e.g. Bearer {{token}}, or bare {{token}}
FieldTypeRequired
namestringyes
locationstringyes — URL or file path to the OpenAPI document
routeMatcherRouteMatcherRefno
FieldTypeRequiredNotes
namestringyes
kindkong | nginx | envoyyes
configstringyesThe raw gateway config, stored inline
overrides{ upstream, from, to }[]noAll three required. from is the backend path, to the gateway-facing one
transcodesbooleannogRPC↔JSON transcoding; auto-detected for Envoy
FieldTypeRequiredNotes
matcherstringyes
upstreamstringnoOmit to auto-match the spec’s server URL against route upstreams
gatewayUrlstringnoscheme://host[:port]; {{var}} allowed

The original linear chaining path. It is still in the format, but chains are the chaining surface in the app.

TypeFieldRequired
Pipelinenameyes
stepsno
PipelineSteprequest — a Request name in the same collectionyes
extractno
FieldTypeRequiredNotes
varstringyes
frombody | header | statusyes
pathstringnoDotted JSON path for body, header name for header, unused for status

A separate file — .tinspec/chains/<name>.yaml, formatVersion: 3.

FieldTypeRequired
formatVersionnumberyes
namestringyes
nodesChainNode[]yes
edgesChainEdge[]yes
FieldTypeRequiredApplies to
idstringyesall — edges reference it
typesee belowyesall
position{ x, y }, both requiredyesall
requestChainRequestRefnorequest
extractExtraction[]norequest
utility{ fn, var, args? }nogenerator
rules{ expr?, port, label? }[]norouter — ordered, first match wins; an empty expr always matches
configmap<string, string>noPer-kind settings
scriptstringnoscriptasync function run(vars, ctx); returned keys become outputs
assertionsAssertion[]noassert
fields{ var, value }[]noset
portsNodePortsnoall

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: inputvalue; environmentkey; mathexpr, var; transformop, expr, var; conditionexpr; delayms; loopmode, count, listVar, itemVar, indexVar; aggregatefromVar, 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.

FieldTypeRequiredNotes
idstringyes
sourcestringyes
targetstringyes
sourceHandlestringnotrue/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.