{
  "schema": "signoff.studio/collection/v1",
  "id": "col_signoff_studio_starter",
  "name": "Signoff Studio Starter Guide",
  "description": "Official starter collection for the Signoff Studio docs. Uses httpbingo.org via the httpbingo environment. Work through Quick Start in order.",
  "variables": [
    {
      "id": "kv_cvar-suitename-0_qmlb6r",
      "key": "suiteName",
      "value": "signoff-starter",
      "initialValue": "signoff-starter",
      "enabled": true,
      "secret": false
    },
    {
      "id": "kv_cvar-maxresponsems-1_vnz1hi",
      "key": "maxResponseMs",
      "value": "8000",
      "initialValue": "8000",
      "enabled": true,
      "secret": false
    },
    {
      "id": "kv_cvar-correlationid-2_iovp2x",
      "key": "correlationId",
      "value": "sig-static-fallback",
      "initialValue": "sig-static-fallback",
      "enabled": true,
      "secret": false
    },
    {
      "id": "kv_cvar-chainuuid-3_jbxjfr",
      "key": "chainUuid",
      "value": "00000000-0000-0000-0000-000000000000",
      "initialValue": "00000000-0000-0000-0000-000000000000",
      "enabled": true,
      "secret": false
    },
    {
      "id": "kv_cvar-bulkpayload-4_upm8va",
      "key": "bulkPayload",
      "value": "{}",
      "initialValue": "{}",
      "enabled": true,
      "secret": false
    }
  ],
  "preRequestScript": "// Collection-level pre-request: give every run a stable correlation id.\nif (!qa.collectionVariables.get(\"correlationId\")) {\n  qa.collectionVariables.set(\"correlationId\", \"sig-\" + Date.now());\n}",
  "testScript": "",
  "items": [
    {
      "type": "folder",
      "id": "folder_01-get-query",
      "name": "01 - GET & Query",
      "description": "",
      "preRequestScript": "",
      "testScript": "",
      "items": [
        {
          "type": "request",
          "id": "req_01-get-query__get-query-params",
          "name": "GET · Query Params",
          "description": "Baseline GET. Proves query strings survive template interpolation.",
          "method": "GET",
          "url": "{{baseUrl}}/get?product=signoff&suite={{suiteName}}&page=1",
          "params": [
            {
              "id": "kv_empty-param_1i6qz1",
              "key": "",
              "value": "",
              "enabled": true
            }
          ],
          "headers": [
            {
              "id": "kv_h-accept-0_ctq5io",
              "key": "Accept",
              "value": "application/json",
              "enabled": true
            }
          ],
          "body": "",
          "bodyType": "none",
          "formFields": [],
          "graphqlVariables": "",
          "auth": {
            "type": "none",
            "params": {}
          },
          "preRequestScript": "",
          "testScript": "qa.test(\"Status is 200 OK\", function () {\n  qa.response.to.have.status(200);\n});\n\nqa.test(\"Response body is JSON\", function () {\n  qa.response.to.be.json;\n});\n\nvar args = (qa.response.json() || {}).args || {};\n\nqa.test(\"Literal query params echoed back\", function () {\n  qa.expect(args.product).to.equal(\"signoff\");\n  qa.expect(args.page).to.equal(\"1\");\n});\n\nqa.test(\"Templated query param was interpolated\", function () {\n  qa.expect(args.suite).to.equal(qa.variables.get(\"suiteName\"));\n  qa.expect(String(args.suite).indexOf(\"{{\")).to.equal(-1);\n});\n\nqa.test(\"Responded inside the latency budget\", function () {\n  var budget = Number(qa.variables.get(\"maxResponseMs\") || 8000);\n  qa.expect(qa.response.responseTime < budget).to.be.true;\n});",
          "settings": {
            "followRedirects": true,
            "sslVerification": true,
            "timeout": 30000,
            "sendCookies": true,
            "storeCookies": true,
            "localProxyFallback": true
          }
        },
        {
          "type": "request",
          "id": "req_01-get-query__get-request-header-echo",
          "name": "GET · Request Header Echo",
          "description": "Verifies custom headers are forwarded, templated header values are interpolated, and disabled rows are dropped.",
          "method": "GET",
          "url": "{{baseUrl}}/headers",
          "params": [
            {
              "id": "kv_empty-param_1i6qz1",
              "key": "",
              "value": "",
              "enabled": true
            }
          ],
          "headers": [
            {
              "id": "kv_h-accept-0_ctq5io",
              "key": "Accept",
              "value": "application/json",
              "enabled": true
            },
            {
              "id": "kv_h-x-signoff-client-1_ptbi70",
              "key": "X-Signoff-Client",
              "value": "signoff-studio",
              "enabled": true
            },
            {
              "id": "kv_h-x-signoff-suite-2_kn1w1k",
              "key": "X-Signoff-Suite",
              "value": "{{suiteName}}",
              "enabled": true
            },
            {
              "id": "kv_h-x-correlation-id-3_n0or96",
              "key": "X-Correlation-Id",
              "value": "{{correlationId}}",
              "enabled": true
            },
            {
              "id": "kv_h-x-disabled-header-4_ygtf5h",
              "key": "X-Disabled-Header",
              "value": "should-not-be-sent",
              "enabled": false
            }
          ],
          "body": "",
          "bodyType": "none",
          "formFields": [],
          "graphqlVariables": "",
          "auth": {
            "type": "none",
            "params": {}
          },
          "preRequestScript": "",
          "testScript": "var sent = (qa.response.json() || {}).headers || {};\n\nfunction header(name) {\n  var keys = Object.keys(sent);\n  for (var i = 0; i < keys.length; i++) {\n    if (keys[i].toLowerCase() === String(name).toLowerCase()) return (Array.isArray(sent[keys[i]]) ? sent[keys[i]][0] : sent[keys[i]]);\n  }\n  return undefined;\n}\n\nqa.test(\"Status is 200 OK\", function () {\n  qa.response.to.have.status(200);\n});\n\nqa.test(\"Static custom header forwarded\", function () {\n  qa.expect(header(\"X-Signoff-Client\")).to.equal(\"signoff-studio\");\n});\n\nqa.test(\"Templated header value interpolated\", function () {\n  qa.expect(header(\"X-Signoff-Suite\")).to.equal(qa.variables.get(\"suiteName\"));\n});\n\nqa.test(\"Correlation id header resolved\", function () {\n  var cid = header(\"X-Correlation-Id\");\n  qa.expect(String(cid || \"\")).to.be.a(\"string\");\n  qa.expect(String(cid)).to.not.equal(\"\");\n  qa.expect(String(cid).indexOf(\"{{\")).to.equal(-1);\n});\n\nqa.test(\"Disabled header row was not sent\", function () {\n  qa.expect(header(\"X-Disabled-Header\")).to.equal(undefined);\n});\n\nqa.test(\"Response Content-Type is JSON\", function () {\n  qa.expect(String(qa.response.headers.get(\"content-type\"))).to.include(\"application/json\");\n});",
          "settings": {
            "followRedirects": true,
            "sslVerification": true,
            "timeout": 30000,
            "sendCookies": true,
            "storeCookies": true,
            "localProxyFallback": true
          }
        }
      ]
    },
    {
      "type": "folder",
      "id": "folder_02-json-bodies",
      "name": "02 - JSON Bodies",
      "description": "",
      "preRequestScript": "",
      "testScript": "",
      "items": [
        {
          "type": "request",
          "id": "req_02-json-bodies__post-json-create",
          "name": "POST · JSON Create",
          "description": "JSON body with scalars, array and nested object. Content-Type must be auto-set.",
          "method": "POST",
          "url": "{{baseUrl}}/post",
          "params": [
            {
              "id": "kv_empty-param_1i6qz1",
              "key": "",
              "value": "",
              "enabled": true
            }
          ],
          "headers": [
            {
              "id": "kv_h-accept-0_ctq5io",
              "key": "Accept",
              "value": "application/json",
              "enabled": true
            }
          ],
          "body": "{\n  \"name\": \"{{userName}}\",\n  \"email\": \"{{userEmail}}\",\n  \"role\": \"{{userRole}}\",\n  \"active\": true,\n  \"tags\": [\"signoff\", \"loadtest\"],\n  \"profile\": { \"team\": \"qa\", \"level\": 3 }\n}",
          "bodyType": "json",
          "formFields": [],
          "graphqlVariables": "",
          "auth": {
            "type": "none",
            "params": {}
          },
          "preRequestScript": "",
          "testScript": "qa.test(\"Status is 200 OK\", function () {\n  qa.response.to.have.status(200);\n});\n\nvar body = qa.response.json();\nvar sent = body.headers || {};\nvar contentType = sent[\"Content-Type\"] || sent[\"content-type\"];\n\nqa.test(\"Content-Type auto-set to application/json\", function () {\n  qa.expect(String(contentType)).to.include(\"application/json\");\n});\n\nqa.test(\"Server parsed the payload as JSON\", function () {\n  qa.expect(body.json).to.be.an(\"object\");\n});\n\nqa.test(\"Scalar fields round-tripped\", function () {\n  qa.expect(body.json.name).to.equal(qa.variables.get(\"userName\"));\n  qa.expect(body.json.email).to.equal(qa.variables.get(\"userEmail\"));\n  qa.expect(body.json.role).to.equal(qa.variables.get(\"userRole\"));\n  qa.expect(body.json.active).to.be.true;\n});\n\nqa.test(\"Array preserved with correct length and contents\", function () {\n  qa.expect(body.json.tags).to.be.an(\"array\");\n  qa.expect(body.json.tags.length).to.equal(2);\n  qa.expect(body.json.tags).to.include(\"signoff\");\n});\n\nqa.test(\"Nested object preserved with types intact\", function () {\n  qa.expect(body.json.profile).to.be.an(\"object\");\n  qa.expect(body.json.profile.team).to.equal(\"qa\");\n  qa.expect(body.json.profile.level).to.equal(3);\n});\n\nqa.test(\"No unresolved variables leaked into the raw body\", function () {\n  qa.expect(String(body.data).indexOf(\"{{\")).to.equal(-1);\n});",
          "settings": {
            "followRedirects": true,
            "sslVerification": true,
            "timeout": 30000,
            "sendCookies": true,
            "storeCookies": true,
            "localProxyFallback": true
          }
        },
        {
          "type": "request",
          "id": "req_02-json-bodies__put-json-full-replace",
          "name": "PUT · JSON Full Replace",
          "description": "",
          "method": "PUT",
          "url": "{{baseUrl}}/put",
          "params": [
            {
              "id": "kv_empty-param_1i6qz1",
              "key": "",
              "value": "",
              "enabled": true
            }
          ],
          "headers": [
            {
              "id": "kv_h-accept-0_ctq5io",
              "key": "Accept",
              "value": "application/json",
              "enabled": true
            }
          ],
          "body": "{\n  \"id\": \"{{userId}}\",\n  \"name\": \"{{userName}}\",\n  \"email\": \"{{userEmail}}\",\n  \"role\": \"{{userRole}}\",\n  \"replaced\": true\n}",
          "bodyType": "json",
          "formFields": [],
          "graphqlVariables": "",
          "auth": {
            "type": "none",
            "params": {}
          },
          "preRequestScript": "",
          "testScript": "qa.test(\"Status is 200 OK\", function () {\n  qa.response.to.have.status(200);\n});\n\nvar body = qa.response.json();\n\nqa.test(\"Request was routed to the PUT endpoint\", function () {\n  qa.expect(String(body.url)).to.include(\"/put\");\n});\n\nqa.test(\"PUT carried a JSON body\", function () {\n  qa.expect(body.json).to.be.an(\"object\");\n  qa.expect(body.json.replaced).to.be.true;\n});\n\nqa.test(\"All five fields present after a full replace\", function () {\n  qa.expect(Object.keys(body.json).length).to.equal(5);\n  qa.expect(body.json.id).to.equal(qa.variables.get(\"userId\"));\n});",
          "settings": {
            "followRedirects": true,
            "sslVerification": true,
            "timeout": 30000,
            "sendCookies": true,
            "storeCookies": true,
            "localProxyFallback": true
          }
        }
      ]
    },
    {
      "type": "folder",
      "id": "folder_05-auth",
      "name": "05 - Auth",
      "description": "",
      "preRequestScript": "",
      "testScript": "",
      "items": [
        {
          "type": "request",
          "id": "req_05-auth__get-bearer-token",
          "name": "GET · Bearer Token",
          "description": "",
          "method": "GET",
          "url": "{{baseUrl}}/bearer",
          "params": [
            {
              "id": "kv_empty-param_1i6qz1",
              "key": "",
              "value": "",
              "enabled": true
            }
          ],
          "headers": [
            {
              "id": "kv_h-accept-0_ctq5io",
              "key": "Accept",
              "value": "application/json",
              "enabled": true
            }
          ],
          "body": "",
          "bodyType": "none",
          "formFields": [],
          "graphqlVariables": "",
          "auth": {
            "type": "bearer",
            "params": {
              "token": "{{bearerToken}}"
            }
          },
          "preRequestScript": "",
          "testScript": "qa.test(\"Status is 200 OK\", function () {\n  qa.response.to.have.status(200);\n});\n\nvar body = qa.response.json();\n\nqa.test(\"Server accepted the bearer token\", function () {\n  qa.expect(body.authenticated).to.be.true;\n});\n\nqa.test(\"Token value matches the variable, fully resolved\", function () {\n  qa.expect(body.token).to.equal(qa.variables.get(\"bearerToken\"));\n  qa.expect(String(body.token).indexOf(\"{{\")).to.equal(-1);\n});",
          "settings": {
            "followRedirects": true,
            "sslVerification": true,
            "timeout": 30000,
            "sendCookies": true,
            "storeCookies": true,
            "localProxyFallback": true
          }
        },
        {
          "type": "request",
          "id": "req_05-auth__get-basic-auth",
          "name": "GET · Basic Auth",
          "description": "The endpoint only returns 200 when the Basic header is correctly base64 encoded.",
          "method": "GET",
          "url": "{{baseUrl}}/basic-auth/{{basicUser}}/{{basicPass}}",
          "params": [
            {
              "id": "kv_empty-param_1i6qz1",
              "key": "",
              "value": "",
              "enabled": true
            }
          ],
          "headers": [
            {
              "id": "kv_h-accept-0_ctq5io",
              "key": "Accept",
              "value": "application/json",
              "enabled": true
            }
          ],
          "body": "",
          "bodyType": "none",
          "formFields": [],
          "graphqlVariables": "",
          "auth": {
            "type": "basic",
            "params": {
              "username": "{{basicUser}}",
              "password": "{{basicPass}}"
            }
          },
          "preRequestScript": "",
          "testScript": "qa.test(\"Status is 200 OK — credentials were encoded correctly\", function () {\n  qa.response.to.have.status(200);\n});\n\nvar body = qa.response.json();\n\nqa.test(\"Server reports the request as authenticated\", function () {\n  qa.expect(body.authenticated).to.be.true;\n});\n\nqa.test(\"Authenticated user matches the variable\", function () {\n  qa.expect(body.user).to.equal(qa.variables.get(\"basicUser\"));\n});",
          "settings": {
            "followRedirects": true,
            "sslVerification": true,
            "timeout": 30000,
            "sendCookies": true,
            "storeCookies": true,
            "localProxyFallback": true
          }
        },
        {
          "type": "request",
          "id": "req_05-auth__get-api-key-header",
          "name": "GET · API Key Header",
          "description": "",
          "method": "GET",
          "url": "{{baseUrl}}/headers",
          "params": [
            {
              "id": "kv_empty-param_1i6qz1",
              "key": "",
              "value": "",
              "enabled": true
            }
          ],
          "headers": [
            {
              "id": "kv_h-accept-0_ctq5io",
              "key": "Accept",
              "value": "application/json",
              "enabled": true
            }
          ],
          "body": "",
          "bodyType": "none",
          "formFields": [],
          "graphqlVariables": "",
          "auth": {
            "type": "api-key",
            "params": {
              "key": "{{apiKeyName}}",
              "value": "{{apiKeyValue}}",
              "in": "header"
            }
          },
          "preRequestScript": "",
          "testScript": "qa.test(\"Status is 200 OK\", function () {\n  qa.response.to.have.status(200);\n});\n\nvar sent = (qa.response.json() || {}).headers || {};\nvar wanted = String(qa.variables.get(\"apiKeyName\"));\n\nfunction header(name) {\n  var keys = Object.keys(sent);\n  for (var i = 0; i < keys.length; i++) {\n    if (keys[i].toLowerCase() === String(name).toLowerCase()) return (Array.isArray(sent[keys[i]]) ? sent[keys[i]][0] : sent[keys[i]]);\n  }\n  return undefined;\n}\n\nqa.test(\"API key was injected as a request header\", function () {\n  qa.expect(header(wanted) !== undefined).to.be.true;\n});\n\nqa.test(\"API key value fully resolved\", function () {\n  qa.expect(header(wanted)).to.equal(qa.variables.get(\"apiKeyValue\"));\n});\n\nqa.test(\"API key was not also leaked into the query string\", function () {\n  qa.expect(String(qa.response.json().url || \"\").indexOf(wanted)).to.equal(-1);\n});",
          "settings": {
            "followRedirects": true,
            "sslVerification": true,
            "timeout": 30000,
            "sendCookies": true,
            "storeCookies": true,
            "localProxyFallback": true
          }
        }
      ]
    },
    {
      "type": "folder",
      "id": "folder_06-status-codes-negative",
      "name": "06 - Status Codes & Negative",
      "description": "",
      "preRequestScript": "",
      "testScript": "",
      "items": [
        {
          "type": "request",
          "id": "req_06-status-codes-negative__get-404-not-found",
          "name": "GET · 404 Not Found",
          "description": "",
          "method": "GET",
          "url": "{{baseUrl}}/status/404",
          "params": [
            {
              "id": "kv_empty-param_1i6qz1",
              "key": "",
              "value": "",
              "enabled": true
            }
          ],
          "headers": [
            {
              "id": "kv_empty-header_s3rf8z",
              "key": "",
              "value": "",
              "enabled": true
            }
          ],
          "body": "",
          "bodyType": "none",
          "formFields": [],
          "graphqlVariables": "",
          "auth": {
            "type": "none",
            "params": {}
          },
          "preRequestScript": "",
          "testScript": "qa.test(\"Status is 404 as designed\", function () {\n  qa.response.to.have.status(404);\n});\n\nqa.test(\"404 did not raise a network error\", function () {\n  qa.expect(qa.response.code).to.equal(404);\n});",
          "settings": {
            "followRedirects": true,
            "sslVerification": true,
            "timeout": 30000,
            "sendCookies": true,
            "storeCookies": true,
            "localProxyFallback": true
          }
        }
      ]
    }
  ]
}