{
  "info": {
    "_postman_id": "96fa3762-da28-4ab3-abd2-2a58e032ceb5",
    "name": "Caldera REST API v2.2.0 - Full Test Suite",
    "description": "<!--\nGoal: Provide a complete Postman collection aligned with the local OpenAPI contract file api-openapi-2.2.0.yaml.\nRelated sources: /Users/afabre/Documents/Doc Odin 2.0/api-openapi-2.2.0.yaml and /Users/afabre/Documents/Doc Odin 2.0/Docs/Features.md.\nTests in this file: one baseline request-level validation per 2.2.0 operation, covering status codes, basic response shape, key headers for HEAD endpoints, content types for binary endpoints, and id capture for serviceId/deviceId/presetId/imageId/jobId when available.\n-->\n\nThis collection reflects the **2.2.0** API surface described by the local OpenAPI file.\n\nCoverage includes all documented 2.2.0 operations for **services**, **devices**, and **jobs**, including the previously missing job lifecycle and archive endpoints: abort, release, thumbnail, preview, export, and import.\n\nImportant alignment note: the collection intentionally does **not** include `GET /devices/{device}` because that route is not present in the local `api-openapi-2.2.0.yaml` contract.\n\nSuggested execution flow: List services \u2192 Import image into service \u2192 List device(s) \u2192 Return preset(s) \u2192 Submit a new job \u2192 Run job lifecycle and archive requests.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "item": [
    {
      "name": "Services",
      "description": "Service discovery, registration, removal, and image import requests.",
      "item": [
        {
          "name": "List services",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/services",
              "protocol": "{{protocol}}",
              "host": [
                "{{host}}"
              ],
              "port": "{{port}}",
              "path": [
                "v{{version}}",
                "services"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "100"
                },
                {
                  "key": "sort",
                  "value": "name:asc"
                },
                {
                  "key": "state",
                  "value": "running",
                  "disabled": true
                }
              ]
            },
            "description": "Goal: List all available services defined by the 2.2.0 contract.\n\nRelated requests/tests: Count services, Get service, Import image into service\n\nRequired variables: baseUrl\n\nTests in this request:\n\n- status is 200\n\n- body is a JSON array\n\n- first service id is saved into serviceId when available\n\nNotes: The OpenAPI filter parameter is modeled as normal query fields in Postman. Enable or add filter keys as needed."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Goal: validate a successful list response for this resource.",
                  "// Related test cases: status 200, JSON array body, optional first-id capture.",
                  "pm.test('status is 200', function () {",
                  "  pm.response.to.have.status(200);",
                  "});",
                  "pm.test('response body is a JSON array', function () {",
                  "  const data = pm.response.json();",
                  "  pm.expect(data).to.be.an('array');",
                  "});",
                  "// Save the first returned resource id when available to simplify the next requests.",
                  "const data = pm.response.json();",
                  "if (Array.isArray(data) && data.length > 0 && data[0] && data[0].id) { pm.collectionVariables.set('serviceId', data[0].id); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Count services",
          "request": {
            "method": "HEAD",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/services",
              "protocol": "{{protocol}}",
              "host": [
                "{{host}}"
              ],
              "port": "{{port}}",
              "path": [
                "v{{version}}",
                "services"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "100"
                },
                {
                  "key": "sort",
                  "value": "name:asc",
                  "disabled": true
                },
                {
                  "key": "state",
                  "value": "running",
                  "disabled": true
                }
              ]
            },
            "description": "Goal: Count services using the shared pagination/count headers.\n\nRelated requests/tests: List services\n\nRequired variables: baseUrl\n\nTests in this request:\n\n- status is 200\n\n- caldera-total-results header exists"
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Goal: validate a successful count response for a list endpoint.",
                  "// Related test cases: status 200 and pagination/count headers exposed by the API.",
                  "pm.test('status is 200', function () {",
                  "  pm.response.to.have.status(200);",
                  "});",
                  "pm.test('count headers are present', function () {",
                  "  pm.expect(pm.response.headers.has('caldera-total-results')).to.eql(true);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "Add service(s)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/services",
              "protocol": "{{protocol}}",
              "host": [
                "{{host}}"
              ],
              "port": "{{port}}",
              "path": [
                "v{{version}}",
                "services"
              ]
            },
            "description": "Goal: Register one or more remote services.\n\nRelated requests/tests: List services, Get service, Delete service\n\nRequired variables: baseUrl, remoteServiceHost, remoteServiceType, remoteServiceLicense, remoteServicePort\n\nTests in this request:\n\n- status is 201\n\n- body is a JSON array\n\n- first created service id is saved into serviceId when available",
            "body": {
              "mode": "raw",
              "raw": "[\n  {\n    \"host\": \"{{remoteServiceHost}}\",\n    \"type\": \"{{remoteServiceType}}\",\n    \"license\": \"{{remoteServiceLicense}}\",\n    \"port\": {{remoteServicePort}}\n  }\n]",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Goal: validate a successful creation response that returns an array.",
                  "// Related test cases: status check, JSON array body, optional identifier capture.",
                  "pm.test('status is correct', function () {",
                  "  pm.response.to.have.status(201);",
                  "});",
                  "pm.test('response body is a JSON array', function () {",
                  "  const data = pm.response.json();",
                  "  pm.expect(data).to.be.an('array');",
                  "});",
                  "const data = pm.response.json();",
                  "if (Array.isArray(data) && data.length > 0 && data[0] && data[0].id) { pm.collectionVariables.set('serviceId', data[0].id); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Delete all services",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/services",
              "protocol": "{{protocol}}",
              "host": [
                "{{host}}"
              ],
              "port": "{{port}}",
              "path": [
                "v{{version}}",
                "services"
              ]
            },
            "description": "Goal: Unregister all remote services except the REST API service itself.\n\nRelated requests/tests: List services\n\nRequired variables: baseUrl\n\nTests in this request:\n\n- status is 200"
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Goal: validate that the bulk delete request completed successfully.",
                  "// Related test cases: status 200.",
                  "pm.test('status is 200', function () {",
                  "  pm.response.to.have.status(200);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "services/{service}",
          "description": "Requests that operate on one service id.",
          "item": [
            {
              "name": "Get service",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/services/{{serviceId}}",
                  "protocol": "{{protocol}}",
                  "host": [
                    "{{host}}"
                  ],
                  "port": "{{port}}",
                  "path": [
                    "v{{version}}",
                    "services",
                    "{{serviceId}}"
                  ]
                },
                "description": "Goal: Get one service by id.\n\nRelated requests/tests: List services, Add service(s), Delete service\n\nRequired variables: baseUrl, serviceId\n\nTests in this request:\n\n- status is 200 or 403\n\n- successful responses are JSON arrays"
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Goal: validate the single-service endpoint response.",
                      "// Related test cases: status 200 or 403, successful body is a JSON array.",
                      "pm.test('status is allowed', function () {",
                      "  pm.expect([200, 403]).to.include(pm.response.code);",
                      "});",
                      "if (pm.response.code === 200) {",
                      "  pm.test('response body is a JSON array', function () {",
                      "    const data = pm.response.json();",
                      "    pm.expect(data).to.be.an('array');",
                      "  });",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "Delete service",
              "request": {
                "method": "DELETE",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/services/{{serviceId}}",
                  "protocol": "{{protocol}}",
                  "host": [
                    "{{host}}"
                  ],
                  "port": "{{port}}",
                  "path": [
                    "v{{version}}",
                    "services",
                    "{{serviceId}}"
                  ]
                },
                "description": "Goal: Unregister one service by id.\n\nRelated requests/tests: List services, Get service\n\nRequired variables: baseUrl, serviceId\n\nTests in this request:\n\n- status is 200"
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Goal: validate that the service delete request completed successfully.",
                      "// Related test cases: status 200.",
                      "pm.test('status is 200', function () {",
                      "  pm.response.to.have.status(200);",
                      "});"
                    ]
                  }
                }
              ]
            },
            {
              "name": "Import an image into the service",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/services/{{serviceId}}/images",
                  "protocol": "{{protocol}}",
                  "host": [
                    "{{host}}"
                  ],
                  "port": "{{port}}",
                  "path": [
                    "v{{version}}",
                    "services",
                    "{{serviceId}}",
                    "images"
                  ]
                },
                "description": "Goal: Import an image into a service so it can later be submitted as a job.\n\nRelated requests/tests: Get service, Submit a new job to this device\n\nRequired variables: baseUrl, serviceId, imageFileUri\n\nTests in this request:\n\n- status is one of 201/400/405/413/415/500\n\n- successful response is a JSON object\n\n- image id is saved into imageId when available\n\nNotes: Use a file URI such as file:///absolute/path/to/image.png.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"uri\": \"{{imageFileUri}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Goal: validate image import behavior and capture the returned image id.",
                      "// Related test cases: expected status family, successful JSON object body, imageId capture.",
                      "pm.test('status is allowed', function () {",
                      "  pm.expect([201, 400, 405, 413, 415, 500]).to.include(pm.response.code);",
                      "});",
                      "if (pm.response.code === 201) {",
                      "  pm.test('response body is a JSON object', function () {",
                      "    const data = pm.response.json();",
                      "    pm.expect(data).to.be.an('object');",
                      "    pm.expect(data).to.have.property('id');",
                      "    pm.collectionVariables.set(\"imageId\", data.id);",
                      "  });",
                      "}"
                    ]
                  }
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "name": "Devices",
      "description": "Device listing, presets, state changes, submission, and machine information requests.",
      "item": [
        {
          "name": "List device(s)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/devices",
              "protocol": "{{protocol}}",
              "host": [
                "{{host}}"
              ],
              "port": "{{port}}",
              "path": [
                "v{{version}}",
                "devices"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "100"
                },
                {
                  "key": "sort",
                  "value": "name:asc"
                },
                {
                  "key": "state",
                  "value": "running",
                  "disabled": true
                }
              ]
            },
            "description": "Goal: List devices visible through the REST API.\n\nRelated requests/tests: Count device(s), Return preset(s) of a specific device, Return machine information of a specific device\n\nRequired variables: baseUrl\n\nTests in this request:\n\n- status is 200\n\n- body is a JSON array\n\n- first device id is saved into deviceId when available"
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Goal: validate a successful list response for this resource.",
                  "// Related test cases: status 200, JSON array body, optional first-id capture.",
                  "pm.test('status is 200', function () {",
                  "  pm.response.to.have.status(200);",
                  "});",
                  "pm.test('response body is a JSON array', function () {",
                  "  const data = pm.response.json();",
                  "  pm.expect(data).to.be.an('array');",
                  "});",
                  "// Save the first returned resource id when available to simplify the next requests.",
                  "const data = pm.response.json();",
                  "if (Array.isArray(data) && data.length > 0 && data[0] && data[0].id) { pm.collectionVariables.set('deviceId', data[0].id); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Count device(s)",
          "request": {
            "method": "HEAD",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/devices",
              "protocol": "{{protocol}}",
              "host": [
                "{{host}}"
              ],
              "port": "{{port}}",
              "path": [
                "v{{version}}",
                "devices"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "100"
                },
                {
                  "key": "sort",
                  "value": "name:asc",
                  "disabled": true
                },
                {
                  "key": "state",
                  "value": "running",
                  "disabled": true
                }
              ]
            },
            "description": "Goal: Count devices using the shared pagination/count headers.\n\nRelated requests/tests: List device(s)\n\nRequired variables: baseUrl\n\nTests in this request:\n\n- status is 200\n\n- caldera-total-results header exists"
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Goal: validate a successful count response for a list endpoint.",
                  "// Related test cases: status 200 and pagination/count headers exposed by the API.",
                  "pm.test('status is 200', function () {",
                  "  pm.response.to.have.status(200);",
                  "});",
                  "pm.test('count headers are present', function () {",
                  "  pm.expect(pm.response.headers.has('caldera-total-results')).to.eql(true);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "devices/{device}/presets",
          "description": "Preset listing and count requests for one device.",
          "item": [
            {
              "name": "Return preset(s) of a specific device",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/devices/{{deviceId}}/presets",
                  "protocol": "{{protocol}}",
                  "host": [
                    "{{host}}"
                  ],
                  "port": "{{port}}",
                  "path": [
                    "v{{version}}",
                    "devices",
                    "{{deviceId}}",
                    "presets"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "100"
                    },
                    {
                      "key": "sort",
                      "value": "name:asc"
                    },
                    {
                      "key": "name",
                      "value": "*",
                      "disabled": true
                    }
                  ]
                },
                "description": "Goal: List presets for the selected device.\n\nRelated requests/tests: Count preset(s) of a specific device, Submit a new job to this device\n\nRequired variables: baseUrl, deviceId\n\nTests in this request:\n\n- status is 200\n\n- body is a JSON array\n\n- first preset id is saved into presetId when available"
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Goal: validate a successful list response for this resource.",
                      "// Related test cases: status 200, JSON array body, optional first-id capture.",
                      "pm.test('status is 200', function () {",
                      "  pm.response.to.have.status(200);",
                      "});",
                      "pm.test('response body is a JSON array', function () {",
                      "  const data = pm.response.json();",
                      "  pm.expect(data).to.be.an('array');",
                      "});",
                      "// Save the first returned resource id when available to simplify the next requests.",
                      "const data = pm.response.json();",
                      "if (Array.isArray(data) && data.length > 0 && data[0] && data[0].id) { pm.collectionVariables.set('presetId', data[0].id); }"
                    ]
                  }
                }
              ]
            },
            {
              "name": "Count preset(s) of a specific device",
              "request": {
                "method": "HEAD",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/devices/{{deviceId}}/presets",
                  "protocol": "{{protocol}}",
                  "host": [
                    "{{host}}"
                  ],
                  "port": "{{port}}",
                  "path": [
                    "v{{version}}",
                    "devices",
                    "{{deviceId}}",
                    "presets"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "100"
                    },
                    {
                      "key": "sort",
                      "value": "name:asc",
                      "disabled": true
                    },
                    {
                      "key": "name",
                      "value": "*",
                      "disabled": true
                    }
                  ]
                },
                "description": "Goal: Count presets for the selected device.\n\nRelated requests/tests: Return preset(s) of a specific device\n\nRequired variables: baseUrl, deviceId\n\nTests in this request:\n\n- status is 200\n\n- caldera-total-results header exists"
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Goal: validate a successful count response for a list endpoint.",
                      "// Related test cases: status 200 and pagination/count headers exposed by the API.",
                      "pm.test('status is 200', function () {",
                      "  pm.response.to.have.status(200);",
                      "});",
                      "pm.test('count headers are present', function () {",
                      "  pm.expect(pm.response.headers.has('caldera-total-results')).to.eql(true);",
                      "});"
                    ]
                  }
                }
              ]
            }
          ]
        },
        {
          "name": "devices/{device}/state",
          "description": "State change request for one device.",
          "item": [
            {
              "name": "Alter state of a specific device",
              "request": {
                "method": "PUT",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/devices/{{deviceId}}/state",
                  "protocol": "{{protocol}}",
                  "host": [
                    "{{host}}"
                  ],
                  "port": "{{port}}",
                  "path": [
                    "v{{version}}",
                    "devices",
                    "{{deviceId}}",
                    "state"
                  ]
                },
                "description": "Goal: Change the state of the selected device.\n\nRelated requests/tests: List device(s), Return machine information of a specific device\n\nRequired variables: baseUrl, deviceId, deviceState\n\nTests in this request:\n\n- status is one of 200/400/403/404/500\n\nNotes: Supported target values documented in 2.2.0 are running, stopped, and blocked.",
                "body": {
                  "mode": "raw",
                  "raw": "\"{{deviceState}}\"",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Goal: validate the device state change request against the documented status codes.",
                      "// Related test cases: status 200, 400, 403, 404, or 500.",
                      "pm.test('status is allowed', function () {",
                      "  pm.expect([200, 400, 403, 404, 500]).to.include(pm.response.code);",
                      "});"
                    ]
                  }
                }
              ]
            }
          ]
        },
        {
          "name": "devices/{device}/jobs",
          "description": "Job submission request for one device.",
          "item": [
            {
              "name": "Submit a new job to this device",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/devices/{{deviceId}}/jobs",
                  "protocol": "{{protocol}}",
                  "host": [
                    "{{host}}"
                  ],
                  "port": "{{port}}",
                  "path": [
                    "v{{version}}",
                    "devices",
                    "{{deviceId}}",
                    "jobs"
                  ]
                },
                "description": "Goal: Submit a new print job to the selected device.\n\nRelated requests/tests: Import an image into the service, Return preset(s) of a specific device, Get job\n\nRequired variables: baseUrl, deviceId, imageId, presetId\n\nTests in this request:\n\n- status is one of 201/400/500\n\n- successful response is a JSON array\n\n- first job id is saved into jobId when available",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"image\": \"{{imageId}}\",\n  \"preset\": \"{{presetId}}\",\n  \"persistJobInRIP\": true\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Goal: validate job submission and capture the created job id for follow-up requests.",
                      "// Related test cases: status 201/400/500, successful JSON array body, jobId capture.",
                      "pm.test('status is allowed', function () {",
                      "  pm.expect([201, 400, 500]).to.include(pm.response.code);",
                      "});",
                      "if (pm.response.code === 201) {",
                      "  pm.test('response body is a JSON array', function () {",
                      "    const data = pm.response.json();",
                      "    pm.expect(data).to.be.an('array');",
                      "    if (data.length > 0 && data[0].id) { pm.collectionVariables.set(\"jobId\", data[0].id); }",
                      "  });",
                      "}"
                    ]
                  }
                }
              ]
            }
          ]
        },
        {
          "name": "devices/{device}/information",
          "description": "Machine information request for one device.",
          "item": [
            {
              "name": "Return machine information of a specific device",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/devices/{{deviceId}}/information",
                  "protocol": "{{protocol}}",
                  "host": [
                    "{{host}}"
                  ],
                  "port": "{{port}}",
                  "path": [
                    "v{{version}}",
                    "devices",
                    "{{deviceId}}",
                    "information"
                  ]
                },
                "description": "Goal: Read machine-level information for the selected device.\n\nRelated requests/tests: List device(s), Alter state of a specific device\n\nRequired variables: baseUrl, deviceId\n\nTests in this request:\n\n- status is 200 or 503\n\n- successful responses are JSON objects"
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Goal: validate the machine information endpoint response.",
                      "// Related test cases: status 200 or 503, successful body is a JSON object.",
                      "pm.test('status is allowed', function () {",
                      "  pm.expect([200, 503]).to.include(pm.response.code);",
                      "});",
                      "if (pm.response.code === 200) {",
                      "  pm.test('response body is a JSON object', function () {",
                      "    const data = pm.response.json();",
                      "    pm.expect(data).to.be.an('object');",
                      "  });",
                      "}"
                    ]
                  }
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "name": "Jobs",
      "description": "Job listing, lifecycle, preview, export, and import requests.",
      "item": [
        {
          "name": "Retrieve job(s)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/jobs",
              "protocol": "{{protocol}}",
              "host": [
                "{{host}}"
              ],
              "port": "{{port}}",
              "path": [
                "v{{version}}",
                "jobs"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "100"
                },
                {
                  "key": "sort",
                  "value": "name:asc"
                },
                {
                  "key": "state",
                  "value": "running",
                  "disabled": true
                },
                {
                  "key": "name",
                  "value": "*",
                  "disabled": true
                }
              ]
            },
            "description": "Goal: List jobs visible through the REST API.\n\nRelated requests/tests: Count job(s), Get job\n\nRequired variables: baseUrl\n\nTests in this request:\n\n- status is 200\n\n- body is a JSON array\n\n- first job id is saved into jobId when available"
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Goal: validate a successful list response for this resource.",
                  "// Related test cases: status 200, JSON array body, optional first-id capture.",
                  "pm.test('status is 200', function () {",
                  "  pm.response.to.have.status(200);",
                  "});",
                  "pm.test('response body is a JSON array', function () {",
                  "  const data = pm.response.json();",
                  "  pm.expect(data).to.be.an('array');",
                  "});",
                  "// Save the first returned resource id when available to simplify the next requests.",
                  "const data = pm.response.json();",
                  "if (Array.isArray(data) && data.length > 0 && data[0] && data[0].id) { pm.collectionVariables.set('jobId', data[0].id); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Count job(s)",
          "request": {
            "method": "HEAD",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/jobs",
              "protocol": "{{protocol}}",
              "host": [
                "{{host}}"
              ],
              "port": "{{port}}",
              "path": [
                "v{{version}}",
                "jobs"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "100"
                },
                {
                  "key": "sort",
                  "value": "name:asc",
                  "disabled": true
                },
                {
                  "key": "state",
                  "value": "running",
                  "disabled": true
                },
                {
                  "key": "name",
                  "value": "*",
                  "disabled": true
                }
              ]
            },
            "description": "Goal: Count jobs using the shared pagination/count headers.\n\nRelated requests/tests: Retrieve job(s)\n\nRequired variables: baseUrl\n\nTests in this request:\n\n- status is 200\n\n- caldera-total-results header exists"
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Goal: validate a successful count response for a list endpoint.",
                  "// Related test cases: status 200 and pagination/count headers exposed by the API.",
                  "pm.test('status is 200', function () {",
                  "  pm.response.to.have.status(200);",
                  "});",
                  "pm.test('count headers are present', function () {",
                  "  pm.expect(pm.response.headers.has('caldera-total-results')).to.eql(true);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "jobs/{job}",
          "description": "Requests that operate on one job id.",
          "item": [
            {
              "name": "Get job",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/jobs/{{jobId}}",
                  "protocol": "{{protocol}}",
                  "host": [
                    "{{host}}"
                  ],
                  "port": "{{port}}",
                  "path": [
                    "v{{version}}",
                    "jobs",
                    "{{jobId}}"
                  ]
                },
                "description": "Goal: Get one job by id.\n\nRelated requests/tests: Retrieve job(s), Submit a new job to this device, Delete job\n\nRequired variables: baseUrl, jobId\n\nTests in this request:\n\n- status is 200 or 403\n\n- successful responses are JSON objects"
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Goal: validate the single-job endpoint response.",
                      "// Related test cases: status 200 or 403, successful body is a JSON object.",
                      "pm.test('status is allowed', function () {",
                      "  pm.expect([200, 403]).to.include(pm.response.code);",
                      "});",
                      "if (pm.response.code === 200) {",
                      "  pm.test('response body is a JSON object', function () {",
                      "    const data = pm.response.json();",
                      "    pm.expect(data).to.be.an('object');",
                      "    pm.expect(data).to.have.property(\"id\");",
                      "  });",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "Delete job",
              "request": {
                "method": "DELETE",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/jobs/{{jobId}}",
                  "protocol": "{{protocol}}",
                  "host": [
                    "{{host}}"
                  ],
                  "port": "{{port}}",
                  "path": [
                    "v{{version}}",
                    "jobs",
                    "{{jobId}}"
                  ]
                },
                "description": "Goal: Delete one job by id.\n\nRelated requests/tests: Get job, Retrieve job(s)\n\nRequired variables: baseUrl, jobId\n\nTests in this request:\n\n- status is one of 200/404/502"
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Goal: validate the job delete request against the documented status codes.",
                      "// Related test cases: status 200, 404, or 502.",
                      "pm.test('status is allowed', function () {",
                      "  pm.expect([200, 404, 502]).to.include(pm.response.code);",
                      "});"
                    ]
                  }
                }
              ]
            },
            {
              "name": "Abort job",
              "request": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/jobs/{{jobId}}/abort",
                  "protocol": "{{protocol}}",
                  "host": [
                    "{{host}}"
                  ],
                  "port": "{{port}}",
                  "path": [
                    "v{{version}}",
                    "jobs",
                    "{{jobId}}",
                    "abort"
                  ]
                },
                "description": "Goal: Abort a running job.\n\nRelated requests/tests: Get job, Release job\n\nRequired variables: baseUrl, jobId\n\nTests in this request:\n\n- status is one of 200/204/404/502\n\n- successful 200 responses are JSON objects"
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Goal: validate job abort behavior.",
                      "// Related test cases: status 200/204/404/502, successful 200 body is a JSON object.",
                      "pm.test('status is allowed', function () {",
                      "  pm.expect([200, 204, 404, 502]).to.include(pm.response.code);",
                      "});",
                      "if (pm.response.code === 200) {",
                      "  pm.test('response body is a JSON object', function () {",
                      "    const data = pm.response.json();",
                      "    pm.expect(data).to.be.an('object');",
                      "  });",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "Release job",
              "request": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/jobs/{{jobId}}/release",
                  "protocol": "{{protocol}}",
                  "host": [
                    "{{host}}"
                  ],
                  "port": "{{port}}",
                  "path": [
                    "v{{version}}",
                    "jobs",
                    "{{jobId}}",
                    "release"
                  ]
                },
                "description": "Goal: Release a blocked job.\n\nRelated requests/tests: Get job, Abort job\n\nRequired variables: baseUrl, jobId\n\nTests in this request:\n\n- status is one of 200/404/502\n\n- successful 200 responses are JSON objects"
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Goal: validate job release behavior.",
                      "// Related test cases: status 200/404/502, successful 200 body is a JSON object.",
                      "pm.test('status is allowed', function () {",
                      "  pm.expect([200, 404, 502]).to.include(pm.response.code);",
                      "});",
                      "if (pm.response.code === 200) {",
                      "pm.test('response body is a JSON object', function () {",
                      "    const data = pm.response.json();",
                      "    pm.expect(data).to.be.an('object');",
                      "  });",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "Get thumbnail",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "image/png"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/jobs/{{jobId}}/thumbnail",
                  "protocol": "{{protocol}}",
                  "host": [
                    "{{host}}"
                  ],
                  "port": "{{port}}",
                  "path": [
                    "v{{version}}",
                    "jobs",
                    "{{jobId}}",
                    "thumbnail"
                  ]
                },
                "description": "Goal: Download the job thumbnail image.\n\nRelated requests/tests: Get preview, Get job\n\nRequired variables: baseUrl, jobId\n\nTests in this request:\n\n- status is one of 200/404/502\n\n- successful responses expose an image/png content type"
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Goal: validate thumbnail download behavior.",
                      "// Related test cases: status 200/404/502 and image/png content type on success.",
                      "pm.test('status is allowed', function () {",
                      "  pm.expect([200, 404, 502]).to.include(pm.response.code);",
                      "});",
                      "if (pm.response.code === 200) {",
                      "  pm.test('content-type contains image/png', function () {",
                      "    pm.expect((pm.response.headers.get('Content-Type') || '').toLowerCase()).to.include('image/png');",
                      "  });",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "Get thumbnail preview",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/octet-stream"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/jobs/{{jobId}}/preview",
                  "protocol": "{{protocol}}",
                  "host": [
                    "{{host}}"
                  ],
                  "port": "{{port}}",
                  "path": [
                    "v{{version}}",
                    "jobs",
                    "{{jobId}}",
                    "preview"
                  ]
                },
                "description": "Goal: Download the job preview stream.\n\nRelated requests/tests: Get thumbnail, Export a job\n\nRequired variables: baseUrl, jobId\n\nTests in this request:\n\n- status is one of 200/404/502\n\n- successful responses expose an octet-stream content type"
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Goal: validate preview stream download behavior.",
                      "// Related test cases: status 200/404/502 and application/octet-stream content type on success.",
                      "pm.test('status is allowed', function () {",
                      "  pm.expect([200, 404, 502]).to.include(pm.response.code);",
                      "});",
                      "if (pm.response.code === 200) {",
                      "  pm.test('content-type contains octet-stream', function () {",
                      "    pm.expect((pm.response.headers.get('Content-Type') || '').toLowerCase()).to.include('application/octet-stream');",
                      "  });",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "Export a job",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/octet-stream"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/jobs/{{jobId}}/export",
                  "protocol": "{{protocol}}",
                  "host": [
                    "{{host}}"
                  ],
                  "port": "{{port}}",
                  "path": [
                    "v{{version}}",
                    "jobs",
                    "{{jobId}}",
                    "export"
                  ],
                  "query": [
                    {
                      "key": "includeRippedFiles",
                      "value": "{{includeRippedFiles}}"
                    },
                    {
                      "key": "includeSourceFiles",
                      "value": "{{includeSourceFiles}}"
                    }
                  ]
                },
                "description": "Goal: Export a job archive (.caljob).\n\nRelated requests/tests: Import a job, Get job\n\nRequired variables: baseUrl, jobId, includeRippedFiles, includeSourceFiles\n\nTests in this request:\n\n- status is 200\n\n- successful responses expose an octet-stream content type"
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Goal: validate job export behavior.",
                      "// Related test cases: status 200 and application/octet-stream content type.",
                      "pm.test('status is 200', function () {",
                      "  pm.response.to.have.status(200);",
                      "});",
                      "pm.test('content-type contains octet-stream', function () {",
                      "  pm.expect((pm.response.headers.get('Content-Type') || '').toLowerCase()).to.include('application/octet-stream');",
                      "});"
                    ]
                  }
                }
              ]
            }
          ]
        },
        {
          "name": "Import a job",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/jobs/import",
              "protocol": "{{protocol}}",
              "host": [
                "{{host}}"
              ],
              "port": "{{port}}",
              "path": [
                "v{{version}}",
                "jobs",
                "import"
              ],
              "query": [
                {
                  "key": "deviceId",
                  "value": "{{deviceId}}"
                }
              ]
            },
            "description": "Goal: Import a .caljob archive into a target device.\n\nRelated requests/tests: Export a job, Retrieve job(s)\n\nRequired variables: baseUrl, deviceId, importFilePath, importFileType, importFileSize\n\nTests in this request:\n\n- status is one of 200/400/404/502\n\n- successful responses are JSON objects\n\n- jobId is updated when jobId is returned\n\nNotes: Set importFilePath to a real .caljob file on the machine running Postman.",
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "file",
                  "type": "file",
                  "src": "{{importFilePath}}"
                },
                {
                  "key": "fileType",
                  "type": "text",
                  "value": "{{importFileType}}"
                },
                {
                  "key": "fileSize",
                  "type": "text",
                  "value": "{{importFileSize}}"
                }
              ]
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Goal: validate job import behavior and capture a returned job id when provided.",
                  "// Related test cases: status 200/400/404/502, successful JSON object body, optional jobId capture.",
                  "pm.test('status is allowed', function () {",
                  "  pm.expect([200, 400, 404, 502]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  pm.test('response body is a JSON object', function () {",
                  "    const data = pm.response.json();",
                  "    pm.expect(data).to.be.an('object');",
                  "    if (data.jobId !== undefined && data.jobId !== null) { pm.collectionVariables.set(\"jobId\", String(data.jobId)); }",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    }
  ],
  "variable": [
    {
      "key": "protocol",
      "value": "http"
    },
    {
      "key": "host",
      "value": "localhost"
    },
    {
      "key": "port",
      "value": "12340"
    },
    {
      "key": "version",
      "value": "1"
    },
    {
      "key": "baseUrl",
      "value": "{{protocol}}://{{host}}:{{port}}/v{{version}}"
    },
    {
      "key": "serviceId",
      "value": ""
    },
    {
      "key": "deviceId",
      "value": ""
    },
    {
      "key": "presetId",
      "value": ""
    },
    {
      "key": "imageId",
      "value": ""
    },
    {
      "key": "jobId",
      "value": ""
    },
    {
      "key": "remoteServiceHost",
      "value": "10.59.3.127"
    },
    {
      "key": "remoteServiceType",
      "value": "calderarip"
    },
    {
      "key": "remoteServiceLicense",
      "value": "820718241426951368"
    },
    {
      "key": "remoteServicePort",
      "value": "45344"
    },
    {
      "key": "imageFileUri",
      "value": "file:///tmp/image.png"
    },
    {
      "key": "deviceState",
      "value": "running"
    },
    {
      "key": "includeRippedFiles",
      "value": "false"
    },
    {
      "key": "includeSourceFiles",
      "value": "false"
    },
    {
      "key": "importFilePath",
      "value": "/tmp/job.caljob"
    },
    {
      "key": "importFileType",
      "value": "CALJOB_BASE"
    },
    {
      "key": "importFileSize",
      "value": "204800"
    }
  ]
}
