{"record":{"id":"5cf796e8563e347a","repo":"usestrix/strix","slug":"unexpected-postman-api-response-shape-for-label","errorCode":null,"errorMessage":"Unexpected Postman API response shape for {label}","messagePattern":"Unexpected Postman API response shape for (.+?)","errorType":"exception","errorClass":"SpecParseError","httpStatus":null,"severity":"error","filePath":"strix/utils/api_spec.py","lineNumber":271,"sourceCode":"            timeout=_POSTMAN_FETCH_TIMEOUT,\n        )\n    except requests.RequestException as exc:\n        raise SpecParseError(f\"Failed to reach the Postman API: {exc}\") from exc\n\n    if response.status_code == 401:\n        raise SpecParseError(\"Postman API rejected the key (401). Check POSTMAN_API_KEY.\")\n    if response.status_code == 404:\n        raise SpecParseError(\n            f\"Postman {label} not found (404). Check the id and that the key can access it.\",\n        )\n    if response.status_code != 200:\n        raise SpecParseError(f\"Postman API returned HTTP {response.status_code} for {label}.\")\n    try:\n        payload = response.json()\n    except ValueError as exc:\n        raise SpecParseError(f\"Postman API returned non-JSON for {label}\") from exc\n    if not isinstance(payload, dict):\n        raise SpecParseError(f\"Unexpected Postman API response shape for {label}\")\n    return payload\n\n\ndef fetch_postman_collection(collection_uid: str, api_key: str) -> dict[str, Any]:\n    \"\"\"Fetch a collection from the Postman API and return the raw collection dict.\n\n    Uses ``GET /collections/{uid}`` with the ``X-Api-Key`` header. The endpoint\n    wraps the collection under a ``collection`` key, unwrapped here so the result\n    matches an exported collection file.\n    \"\"\"\n    payload = _postman_api_json(\n        f\"{POSTMAN_API_BASE}/collections/{collection_uid}\",\n        api_key,\n        f\"collection {collection_uid}\",\n    )\n    collection = payload.get(\"collection\", payload)\n    if not isinstance(collection, dict) or not collection:\n        raise SpecParseError(f\"Postman collection {collection_uid} came back empty\")","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/utils/api_spec.py#L253-L289","documentation":"SpecParseError raised when the Postman API returns valid JSON whose top level is not an object (e.g. a JSON array or bare string). The Postman REST API always wraps responses in an object, so this indicates a nonstandard intermediary or an API change.","triggerScenarios":"A mock/stub server standing in for the Postman API that returns a bare array; an API gateway stripping the envelope; Postman shipping a breaking format change.","commonSituations":"Local development against a mocked Postman API; HTTP interception tools returning canned fixtures.","solutions":["Print the raw response to see its actual shape: add a temporary dump or curl the endpoint directly","Fix the mock/fixture to mirror the real envelope ({\"collection\": {...}})","If Postman itself changed shape, pin to a local export until the tool is updated"],"exampleFix":"# before (mock returns)\n[{\"item\": []}]\n\n# after (mock returns the real envelope)\n{\"collection\": {\"info\": {\"_postman_id\": \"...\"}, \"item\": []}}","handlingStrategy":"type-guard","validationCode":"payload = resp.json()\nif not isinstance(payload, dict):\n    raise ValueError(f\"expected object envelope, got {type(payload).__name__}: {str(payload)[:80]}\")","typeGuard":"def is_postman_envelope(p) -> bool:\n    return isinstance(p, dict)","tryCatchPattern":"except SpecParseError as e:\n    if \"response shape\" in str(e):\n        fail loudly with the raw payload attached — this means a mock or gateway is broken, not a transient issue","preventionTips":["Make mocks replicate the real {\"collection\": ...} envelope","Record and replay real Postman responses in tests instead of hand-writing fixtures"],"tags":["postman","schema-shape","mocking"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}