{
  "info": {
    "_postman_id": "160a55c5-c2d4-48f9-b47d-ae4fe785ac55",
    "name": "CDN API — Partner Integration",
    "description": "Every endpoint a partner backend needs: authenticate, upload, list, download, presign, trash.\n\nSetup\n1. Import this collection and the CDN environment file (both downloaded from the portal's Integration Guide).\n2. Select the CDN environment, open it and paste your API key into `api_key` (My API Keys → Reveal → Copy).\n3. Set `bucket` to one of your bucket names (My Buckets → copy icon).\n4. Run 'Who am I' to confirm the key works, then 'Upload File'.\n\nChaining\n'Upload File' stores the returned objectKey in {{object_key}}; every later file request reuses it. 'Presign One File' stores the link in {{presigned_url}} for 'Open Presigned Link'.\n\nEvery request carries a fresh X-Correlation-Id so you can quote it to support.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "item": [
    {
      "name": "1. Authentication",
      "item": [
        {
          "name": "Who am I (key scope)",
          "request": {
            "description": "Returns the tenants and buckets this API key can act on. Use it as a smoke test: 200 means the key is valid; 401 means it is wrong, revoked or expired.",
            "method": "GET",
            "header": [{ "key": "X-Api-Key", "value": "{{api_key}}" }],
            "url": {
              "raw": "{{base_url}}/api/v{{api_version}}/me",
              "host": ["{{base_url}}"],
              "path": ["api", "v{{api_version}}", "me"]
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "2. Files",
      "item": [
        {
          "name": "Upload File",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const body = pm.response.json();",
                  "if (body && body.data && body.data.objectKey) {",
                  "    pm.environment.set('object_key', body.data.objectKey);",
                  "}"
                ],
                "type": "text/javascript"
              }
            }
          ],
          "request": {
            "description": "multipart/form-data. Pick a file in the File field before sending. Saves the returned objectKey into {{object_key}}.",
            "method": "POST",
            "header": [{ "key": "X-Api-Key", "value": "{{api_key}}" }],
            "body": {
              "mode": "formdata",
              "formdata": [
                { "key": "Bucket", "value": "{{bucket}}", "type": "text", "description": "Required. A bucket your key is allowed to use." },
                { "key": "TenantId", "value": "{{tenant_id}}", "type": "text", "description": "Required. Must be a tenant your key is scoped to (see 'Who am I')." },
                { "key": "OwnerId", "value": "{{owner_id}}", "type": "text", "description": "Optional. Your own user/record id, for filtering later." },
                {
                  "key": "FolderPath",
                  "value": "invoices/2026",
                  "type": "text",
                  "description": "Optional. Up to 8 '/'-separated segments of letters, digits, '-' and '_'. Becomes the leading folder of the objectKey.",
                  "disabled": true
                },
                { "key": "File", "type": "file", "value": null, "description": "Required. The file to upload." }
              ]
            },
            "url": {
              "raw": "{{base_url}}/api/v{{api_version}}/files",
              "host": ["{{base_url}}"],
              "path": ["api", "v{{api_version}}", "files"]
            }
          },
          "response": []
        },
        {
          "name": "List Files",
          "request": {
            "description": "Paged, filterable listing scoped to your key's tenants. Enable the optional query params as needed.",
            "method": "GET",
            "header": [{ "key": "X-Api-Key", "value": "{{api_key}}" }],
            "url": {
              "raw": "{{base_url}}/api/v{{api_version}}/files?bucket={{bucket}}&page=1&pageSize=50&sortBy=CreatedAtUTC&sortDescending=true",
              "host": ["{{base_url}}"],
              "path": ["api", "v{{api_version}}", "files"],
              "query": [
                { "key": "bucket", "value": "{{bucket}}" },
                { "key": "page", "value": "1" },
                { "key": "pageSize", "value": "50", "description": "Max 200." },
                { "key": "sortBy", "value": "CreatedAtUTC", "description": "CreatedAtUTC | OriginalFileName | SizeBytes | ContentType | Bucket" },
                { "key": "sortDescending", "value": "true" },
                { "key": "tenantId", "value": "{{tenant_id}}", "description": "Must be a tenant your key is scoped to, else 403.", "disabled": true },
                { "key": "ownerId", "value": "{{owner_id}}", "disabled": true },
                { "key": "objectKeyPrefix", "value": "invoices/", "description": "Folder-style prefix filter.", "disabled": true },
                { "key": "fileNameContains", "value": "invoice", "disabled": true },
                { "key": "contentType", "value": "application/pdf", "disabled": true },
                { "key": "visibility", "value": "Private", "description": "Private | Public", "disabled": true },
                { "key": "createdFromUTC", "value": "2026-01-01T00:00:00Z", "disabled": true },
                { "key": "createdToUTC", "value": "2026-12-31T23:59:59Z", "disabled": true }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Get File Metadata",
          "request": {
            "description": "Size, content type, checksum, thumbnailStatus and thumbnailUrl for one file, without downloading it.",
            "method": "GET",
            "header": [{ "key": "X-Api-Key", "value": "{{api_key}}" }],
            "url": {
              "raw": "{{base_url}}/api/v{{api_version}}/file-metadata/{{bucket}}/{{object_key}}",
              "host": ["{{base_url}}"],
              "path": ["api", "v{{api_version}}", "file-metadata", "{{bucket}}", "{{object_key}}"]
            }
          },
          "response": []
        },
        {
          "name": "Download File (server-to-server)",
          "request": {
            "description": "Streams the bytes, gated by your API key. For backends only; browsers and apps should use a presigned link.",
            "method": "GET",
            "header": [{ "key": "X-Api-Key", "value": "{{api_key}}" }],
            "url": {
              "raw": "{{base_url}}/api/v{{api_version}}/files/{{bucket}}/{{object_key}}",
              "host": ["{{base_url}}"],
              "path": ["api", "v{{api_version}}", "files", "{{bucket}}", "{{object_key}}"]
            }
          },
          "response": []
        },
        {
          "name": "Download File (public bucket, no auth)",
          "request": {
            "description": "Only works for buckets whose visibility is Public. Permanent, cacheable URL. Add ?disposition=inline to display instead of download.",
            "auth": { "type": "noauth" },
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/public/{{public_bucket}}/{{object_key}}",
              "host": ["{{base_url}}"],
              "path": ["public", "{{public_bucket}}", "{{object_key}}"]
            }
          },
          "response": []
        },
        {
          "name": "Delete File (move to trash)",
          "request": {
            "description": "Soft delete. The file moves to trash and can be restored until the retention window ends.",
            "method": "DELETE",
            "header": [{ "key": "X-Api-Key", "value": "{{api_key}}" }],
            "url": {
              "raw": "{{base_url}}/api/v{{api_version}}/files/{{bucket}}/{{object_key}}",
              "host": ["{{base_url}}"],
              "path": ["api", "v{{api_version}}", "files", "{{bucket}}", "{{object_key}}"]
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "3. Presigned Links",
      "item": [
        {
          "name": "Presign One File",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const body = pm.response.json();",
                  "if (body && body.data && body.data.url) {",
                  "    pm.environment.set('presigned_url', body.data.url);",
                  "    console.log('Link expires at ' + body.data.expiresAtUtc);",
                  "}"
                ],
                "type": "text/javascript"
              }
            }
          ],
          "request": {
            "description": "Creates a temporary, signed download link. expirySeconds defaults to 300 and is capped at 3600. After it expires the link returns 410 Gone.",
            "method": "POST",
            "header": [
              { "key": "X-Api-Key", "value": "{{api_key}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bucket\": \"{{bucket}}\",\n  \"objectKey\": \"{{object_key}}\",\n  \"expirySeconds\": 300\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v{{api_version}}/files/presign",
              "host": ["{{base_url}}"],
              "path": ["api", "v{{api_version}}", "files", "presign"]
            }
          },
          "response": []
        },
        {
          "name": "Presign Batch (up to 200)",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const body = pm.response.json();",
                  "if (body && body.data) {",
                  "    console.log('succeeded: ' + body.data.succeeded.length + ', failed: ' + body.data.failed.length);",
                  "    if (body.data.succeeded.length > 0) {",
                  "        pm.environment.set('presigned_url', body.data.succeeded[0].url);",
                  "    }",
                  "}"
                ],
                "type": "text/javascript"
              }
            }
          ],
          "request": {
            "description": "Links for a whole page of files in one call. Returns 200 even when some targets fail; check data.failed (reason: NotFound | Forbidden).",
            "method": "POST",
            "header": [
              { "key": "X-Api-Key", "value": "{{api_key}}" },
              { "key": "Content-Type", "value": "application/json" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"targets\": [\n    { \"bucket\": \"{{bucket}}\", \"objectKey\": \"{{object_key}}\" }\n  ],\n  \"expirySeconds\": 300\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v{{api_version}}/files/presign/batch",
              "host": ["{{base_url}}"],
              "path": ["api", "v{{api_version}}", "files", "presign", "batch"]
            }
          },
          "response": []
        },
        {
          "name": "Open Presigned Link (no auth)",
          "request": {
            "description": "What a browser does with the link: no API key. 200 = file, 410 = link expired (mint a new one), 403 = link was altered.",
            "auth": { "type": "noauth" },
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{presigned_url}}",
              "host": ["{{presigned_url}}"]
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "4. Trash",
      "item": [
        {
          "name": "List Trash",
          "request": {
            "description": "Files you deleted that can still be restored.",
            "method": "GET",
            "header": [{ "key": "X-Api-Key", "value": "{{api_key}}" }],
            "url": {
              "raw": "{{base_url}}/api/v{{api_version}}/trash?bucket={{bucket}}&page=1&pageSize=50",
              "host": ["{{base_url}}"],
              "path": ["api", "v{{api_version}}", "trash"],
              "query": [
                { "key": "bucket", "value": "{{bucket}}" },
                { "key": "page", "value": "1" },
                { "key": "pageSize", "value": "50" }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Restore File",
          "request": {
            "description": "Moves a trashed file back to live. Same bucket and objectKey as before.",
            "method": "POST",
            "header": [{ "key": "X-Api-Key", "value": "{{api_key}}" }],
            "url": {
              "raw": "{{base_url}}/api/v{{api_version}}/trash/restore/{{bucket}}/{{object_key}}",
              "host": ["{{base_url}}"],
              "path": ["api", "v{{api_version}}", "trash", "restore", "{{bucket}}", "{{object_key}}"]
            }
          },
          "response": []
        },
        {
          "name": "Purge File Permanently",
          "request": {
            "description": "Irreversible. Erases a trashed file now instead of waiting for the retention window.",
            "method": "DELETE",
            "header": [{ "key": "X-Api-Key", "value": "{{api_key}}" }],
            "url": {
              "raw": "{{base_url}}/api/v{{api_version}}/trash/{{bucket}}/{{object_key}}",
              "host": ["{{base_url}}"],
              "path": ["api", "v{{api_version}}", "trash", "{{bucket}}", "{{object_key}}"]
            }
          },
          "response": []
        }
      ]
    }
  ],
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "exec": [
          "pm.environment.set('correlation_id', pm.variables.replaceIn('{{$guid}}'));",
          "pm.request.headers.upsert({ key: 'X-Correlation-Id', value: pm.environment.get('correlation_id') });"
        ],
        "type": "text/javascript"
      }
    }
  ]
}
