{
    "variable": [
        {
            "id": "baseUrl",
            "key": "baseUrl",
            "type": "string",
            "name": "string",
            "value": "https:\/\/api.peregrinecdn.com"
        }
    ],
    "info": {
        "name": "Peregrine API",
        "_postman_id": "996eec20-1de9-4849-bbaf-a31d3e62829b",
        "description": "Edge delivery and image optimisation for WordPress.",
        "schema": "https:\/\/schema.getpostman.com\/json\/collection\/v2.1.0\/collection.json",
        "version": "1.1.0"
    },
    "item": [
        {
            "name": "Discovery",
            "description": "",
            "item": [
                {
                    "name": "Liveness.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/health",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/health"
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Answers without a credential, and says nothing a stranger should not see.\nThis is the only route the documentation generator is allowed to call\nfor real; every other example is written by hand, so that generating\ndocs can never reach a customer's data.",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": [
                        {
                            "header": [
                                {
                                    "key": "cache-control",
                                    "value": "no-cache, private"
                                },
                                {
                                    "key": "content-type",
                                    "value": "application\/json"
                                },
                                {
                                    "key": "access-control-allow-origin",
                                    "value": "*"
                                }
                            ],
                            "code": 200,
                            "body": "{\"ok\":true,\"service\":\"peregrine-control-plane\",\"version\":\"1.1.0\",\"db\":\"peregrine\"}",
                            "name": ""
                        }
                    ]
                },
                {
                    "name": "The OpenAPI document.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/openapi.json",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/openapi.json"
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Public, because a client needs to read it before it has a credential, and\nit describes shapes rather than data.",
                        "auth": {
                            "type": "noauth"
                        }
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"openapi\":\"3.0.3\",\"info\":{\"title\":\"Peregrine API\",\"version\":\"1.1.0\"},\"paths\":{}}",
                            "name": ""
                        }
                    ]
                },
                {
                    "name": "What this token can do.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/meta",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/meta"
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "The first call any client should make. Returns the account, the abilities\nthis token actually holds, the sites it can address, the warm-region\ncatalogue, and the conventions of the API - so a program can work without\nbeing told about it in advance."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"version\":\"1.1.0\",\"account\":{\"id\":42,\"name\":\"Acme Digital\",\"kind\":\"agency\",\"plan\":\"dive\"},\"token\":{\"name\":\"Acme automation\",\"authenticated_as\":\"api token\",\"abilities\":[\"read\",\"purge\",\"analyse\"],\"last_used_at\":\"2026-09-15T08:14:02.000000Z\",\"expires_at\":null},\"sites\":[{\"id\":7,\"label\":\"example-co-uk\",\"domain\":\"example.co.uk\",\"status\":\"active\",\"cdn_base\":\"https:\/\/images.example.co.uk\"}],\"warm_regions\":[\"UK\",\"DE\",\"US-East\",\"Singapore\"],\"conventions\":{\"site_reference\":\"Pass the numeric id, the label, or the domain.\",\"bytes\":\"Totals are reconciled server-side. Use them as given.\",\"benchmark\":\"benchmark_modelled is our measured head-to-head, not a live competitor result.\",\"delivery\":\"Markup proves nothing. POST \/v1\/sites\/{site}\/verify is the evidence.\",\"ttfb\":\"Quote the measured improvement between two points, never an absolute figure.\"}}",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 401,
                            "body": "{\"message\":\"Unauthenticated.\"}",
                            "name": "no token, or a revoked one"
                        }
                    ]
                },
                {
                    "name": "Account, plan and entitlement.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/account",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/account"
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Includes what has been paid for. Where no order exists there is no\nceiling, deliberately: blocking someone who has paid costs them what they\nbought today, while letting one extra through costs a little unbilled\nusage."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"id\":42,\"name\":\"Acme Digital\",\"email\":\"ops@acme.example\",\"kind\":\"agency\",\"plan\":\"dive\",\"status\":\"active\",\"entitlement\":{\"sites_bought\":25,\"sites_in_use\":11,\"warm_locations\":3,\"shield\":true},\"usage\":{\"images\":54874,\"bytes\":{\"original\":12020896087,\"optimised\":1169791846,\"saved\":10851104241,\"benchmark_modelled\":2432456692},\"percent\":{\"vs_original\":90,\"vs_benchmark\":52},\"benchmark_note\":null,\"human\":{\"original\":\"11.2 GB\",\"optimised\":\"1.1 GB\",\"saved\":\"10.1 GB\"},\"first_day\":\"2026-08-18\",\"last_day\":\"2026-09-15\"}}",
                            "name": ""
                        }
                    ]
                }
            ]
        },
        {
            "name": "Sites",
            "description": "",
            "item": [
                {
                    "name": "List sites.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/sites",
                            "query": [
                                {
                                    "key": "status",
                                    "value": "active",
                                    "description": "active or paused.",
                                    "disabled": false
                                },
                                {
                                    "key": "delivery_state",
                                    "value": "degraded",
                                    "description": "ok, degraded, down or unknown.",
                                    "disabled": false
                                },
                                {
                                    "key": "q",
                                    "value": "breckland",
                                    "description": "Domain or label substring.",
                                    "disabled": false
                                }
                            ],
                            "raw": "{{baseUrl}}\/api\/v1\/sites?status=active&delivery_state=degraded&q=breckland"
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Every site on the account with its delivery state, DNS state and lifetime\nbyte totals. The right first call for \"which of my sites has a problem\"."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "[{\"id\":7,\"domain\":\"example.co.uk\",\"label\":\"example-co-uk\",\"cdn_base\":\"https:\/\/images.example.co.uk\",\"status\":\"active\",\"delivery_state\":\"ok\",\"dns_state\":\"ok\",\"images\":4223,\"original_bytes\":229983921,\"optimised_bytes\":37648511,\"benchmark_bytes\":54807310}]",
                            "name": ""
                        }
                    ]
                },
                {
                    "name": "Register a site that already delivers.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/sites",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/sites"
                        },
                        "method": "POST",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\"domain\":\"example.co.uk\",\"label\":\"example-co-uk\",\"cdn_base\":\"https:\\\/\\\/images.example.co.uk\",\"path_prefix\":\"wp-content\\\/uploads\"}"
                        },
                        "description": "Writes one row and returns one key. Touches no CDN resource - that is the\nwhole point of it being separate from provisioning. The live estate's\npull zones already exist and pull from an origin rather than storage, so\nprovisioning one of them would build a new storage-backed zone and move\nthe hostname onto it: a full outage arrived at by running the tool that\nlooks right.\n\nRegistration claims a NAME. It proves nothing about who owns the domain,\nand nothing happens until the key is installed on the site and DNS points\nhere. A domain whose only claim is a deleted row is revived for its own\naccount and released for anybody else."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 201,
                            "body": "{\"site\":{\"id\":8,\"label\":\"example-co-uk\",\"domain\":\"example.co.uk\",\"status\":\"active\",\"created_at\":\"2026-09-15T08:20:00.000000Z\",\"delivery\":{\"cdn_base\":null,\"storage_zone\":null,\"path_prefix\":\"wp-content\/uploads\",\"state\":\"unknown\",\"checked_at\":null,\"ok_at\":null,\"fails\":0},\"dns\":{\"state\":\"unknown\",\"checked_at\":null,\"ok_at\":null,\"nudges\":0},\"warm\":{\"status\":\"paused\",\"regions\":[],\"interval_seconds\":604800,\"last_run_at\":null,\"last_warmed\":0,\"last_errors\":0,\"last_note\":null,\"queue_pending\":0,\"pops\":[]},\"key\":{\"prefix\":\"9f2c1ab4\"},\"report\":{\"enabled\":false,\"url\":null},\"usage\":{\"images\":0,\"bytes\":{\"original\":0,\"optimised\":0,\"saved\":0,\"benchmark_modelled\":0},\"percent\":{\"vs_original\":0,\"vs_benchmark\":null},\"benchmark_note\":\"No modelled benchmark recorded for this data.\",\"human\":{\"original\":\"0 B\",\"optimised\":\"0 B\",\"saved\":\"0 B\"},\"first_day\":null,\"last_day\":null}},\"key\":\"9f2c1ab4...shown once and never stored...\",\"warning\":\"Shown once and never stored in plaintext. Paste it into the Peregrine plugin on example.co.uk now.\"}",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 403,
                            "body": "{\"error\":\"missing_ability\",\"ability\":\"sites\",\"detail\":\"This token cannot create, rename, pause and resume sites; rotate site keys. Issue a token with the \\\"sites\\\" ability from the console at https:\/\/app.peregrinecdn.com\/tokens, then retry.\"}",
                            "name": "the token lacks the ability"
                        },
                        {
                            "header": [],
                            "code": 409,
                            "body": "{\"error\":\"site_exists\",\"detail\":\"That domain or label is already in use.\",\"site\":null}",
                            "name": "the domain is already registered"
                        }
                    ]
                },
                {
                    "name": "Everything about one site.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/sites\/:site",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/sites\/:site",
                            "variable": [
                                {
                                    "id": "site",
                                    "key": "site",
                                    "value": "example.co.uk",
                                    "description": "The site: its numeric id, its label, or its\n  domain with or without scheme and \"www.\"."
                                }
                            ]
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Deliberately one call rather than five. The question an operator actually\nasks is \"is this site fine\", and the parts of the answer live in four\ntables - usage, delivery, DNS and warming. Making the caller fetch and\njoin them is how a check gets half done."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"id\":7,\"label\":\"example-co-uk\",\"domain\":\"example.co.uk\",\"status\":\"active\",\"created_at\":\"2026-08-18T09:12:44.000000Z\",\"delivery\":{\"cdn_base\":\"https:\/\/images.example.co.uk\",\"storage_zone\":\"pg-example-co-uk\",\"path_prefix\":\"wp-content\/uploads\",\"state\":\"ok\",\"checked_at\":\"2026-09-15T07:40:11.000000Z\",\"ok_at\":\"2026-09-15T07:40:11.000000Z\",\"fails\":0},\"dns\":{\"state\":\"ok\",\"checked_at\":\"2026-09-15T06:00:00.000000Z\",\"ok_at\":\"2026-08-19T10:02:00.000000Z\",\"nudges\":0},\"warm\":{\"status\":\"active\",\"regions\":[\"UK\",\"DE\"],\"interval_seconds\":604800,\"last_run_at\":\"2026-09-14T02:11:09.000000Z\",\"last_warmed\":812,\"last_errors\":0,\"last_note\":\"purged+rewarmed 812 url(s) across 2 pop(s)\",\"queue_pending\":0,\"pops\":[{\"hostname\":\"example.b-cdn.net\",\"region\":\"UK\",\"pop_code\":\"UK1-1486\",\"edge_ip\":\"203.0.113.10\",\"resolved_at\":\"2026-09-14T02:10:58.000000Z\"}]},\"key\":{\"prefix\":\"9f2c1ab4\"},\"report\":{\"enabled\":true,\"url\":\"https:\/\/app.peregrinecdn.com\/r\/4f1c8a2e9b7d6350a1c2e4f6b8d0a2c4\"},\"usage\":{\"images\":4223,\"bytes\":{\"original\":229983921,\"optimised\":37648511,\"saved\":192335410,\"benchmark_modelled\":54807310},\"percent\":{\"vs_original\":84,\"vs_benchmark\":31},\"benchmark_note\":null,\"human\":{\"original\":\"219 MB\",\"optimised\":\"35.9 MB\",\"saved\":\"183 MB\"},\"first_day\":\"2026-08-18\",\"last_day\":\"2026-09-15\"}}",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 404,
                            "body": "{\"error\":\"site_not_found\",\"ref\":\"exampl.co.uk\",\"known\":[\"example.co.uk\",\"shop.example.co.uk\"],\"detail\":\"No site on this account matches that id, label or domain. The \\\"known\\\" list is what does exist.\"}",
                            "name": "no site on this account matches"
                        }
                    ]
                },
                {
                    "name": "Rename, repoint, pause or resume.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/sites\/:site",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/sites\/:site",
                            "variable": [
                                {
                                    "id": "site",
                                    "key": "site",
                                    "value": "example.co.uk",
                                    "description": "The site: its numeric id, its label, or its\n  domain with or without scheme and \"www.\"."
                                }
                            ]
                        },
                        "method": "PATCH",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\"label\":\"example-co-uk\",\"status\":\"paused\",\"cdn_base\":\"https:\\\/\\\/images.example.co.uk\",\"path_prefix\":\"wp-content\\\/uploads\"}"
                        },
                        "description": "Pausing is a real operation rather than a UI state: a paused site stops\nbeing warmed and stops counting against the site allowance, so it is the\nhonest thing to do with a client who has left."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"id\":7,\"label\":\"example-co-uk\",\"domain\":\"example.co.uk\",\"status\":\"paused\",\"created_at\":\"2026-08-18T09:12:44.000000Z\",\"delivery\":{\"cdn_base\":\"https:\/\/images.example.co.uk\",\"storage_zone\":\"pg-example-co-uk\",\"path_prefix\":\"wp-content\/uploads\",\"state\":\"ok\",\"checked_at\":\"2026-09-15T07:40:11.000000Z\",\"ok_at\":\"2026-09-15T07:40:11.000000Z\",\"fails\":0},\"dns\":{\"state\":\"ok\",\"checked_at\":null,\"ok_at\":null,\"nudges\":0},\"warm\":{\"status\":\"active\",\"regions\":[\"UK\"],\"interval_seconds\":604800,\"last_run_at\":null,\"last_warmed\":0,\"last_errors\":0,\"last_note\":null,\"queue_pending\":0,\"pops\":[]},\"key\":{\"prefix\":\"9f2c1ab4\"},\"report\":{\"enabled\":false,\"url\":null},\"usage\":{\"images\":4223,\"bytes\":{\"original\":229983921,\"optimised\":37648511,\"saved\":192335410,\"benchmark_modelled\":54807310},\"percent\":{\"vs_original\":84,\"vs_benchmark\":31},\"benchmark_note\":null,\"human\":{\"original\":\"219 MB\",\"optimised\":\"35.9 MB\",\"saved\":\"183 MB\"},\"first_day\":\"2026-08-18\",\"last_day\":\"2026-09-15\"}}",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 422,
                            "body": "{\"message\":\"The cdn base field format is invalid.\",\"errors\":{\"cdn_base\":[\"The cdn base field format is invalid.\"]}}",
                            "name": "cdn_base was not a bare hostname"
                        }
                    ]
                },
                {
                    "name": "Stop managing a site.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/sites\/:site",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/sites\/:site",
                            "variable": [
                                {
                                    "id": "site",
                                    "key": "site",
                                    "value": "example.co.uk",
                                    "description": "The site: its numeric id, its label, or its\n  domain with or without scheme and \"www.\"."
                                }
                            ]
                        },
                        "method": "DELETE",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Soft. Usage history is kept, because it is the evidence behind every\nsavings figure already quoted to that client. The CDN zones are\ndeliberately not touched - deleting a pull zone from an API call is how a\nlive site goes dark on a typo."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"deleted\":true,\"detail\":\"Stopped managing example.co.uk. Usage history is kept. Its Bunny zones were not touched - remove them in Bunny if you want them gone.\"}",
                            "name": ""
                        }
                    ]
                },
                {
                    "name": "Rotate the site key.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/sites\/:site\/key",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/sites\/:site\/key",
                            "variable": [
                                {
                                    "id": "site",
                                    "key": "site",
                                    "value": "example.co.uk",
                                    "description": "The site: its numeric id, its label, or its\n  domain with or without scheme and \"www.\"."
                                }
                            ]
                        },
                        "method": "POST",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "The old key stops working the moment this returns, so the plugin on that\nsite is disconnected until the new one is pasted in. Do not do this\nspeculatively. Plaintext is returned once and never stored."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"key\":\"9f2c1ab4...shown once and never stored...\",\"prefix\":\"9f2c1ab4\",\"warning\":\"Shown once. The old key stopped working the moment this returned, so the plugin on example.co.uk is disconnected until this is pasted in.\"}",
                            "name": ""
                        }
                    ]
                },
                {
                    "name": "What is still heavy.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/sites\/:site\/images",
                            "query": [
                                {
                                    "key": "limit",
                                    "value": "20",
                                    "description": "How many in each list, 1-100. Defaults to 20.",
                                    "disabled": false
                                }
                            ],
                            "raw": "{{baseUrl}}\/api\/v1\/sites\/:site\/images?limit=20",
                            "variable": [
                                {
                                    "id": "site",
                                    "key": "site",
                                    "value": "example.co.uk",
                                    "description": "The site: its numeric id, its label, or its\n  domain with or without scheme and \"www.\"."
                                }
                            ]
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Two lists, because they need different fixes. \"heaviest\" is the largest\nfiles a visitor still downloads. \"poorly_compressed\" barely shrank -\nalmost always a photograph saved as a PNG, or something already\ncompressed. No encoder setting fixes the second list; the source file has\nto change.\n\nFigures are the AVIF rows only, which is what a visitor receives."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"site_id\":7,\"domain\":\"example.co.uk\",\"format\":\"avif\",\"note\":\"Figures are the AVIF rows only - what a visitor downloads. WebP fallbacks cover the same originals and are not added in.\",\"heaviest\":[{\"path\":\"\/wp-content\/uploads\/2026\/03\/team-photo.png\",\"original_bytes\":412880,\"optimised_bytes\":380114,\"quality\":45}],\"poorly_compressed\":[{\"path\":\"\/wp-content\/uploads\/2026\/03\/team-photo.png\",\"original_bytes\":412880,\"optimised_bytes\":380114,\"quality\":45}],\"unpathed\":0}",
                            "name": ""
                        }
                    ]
                },
                {
                    "name": "Prove a visitor receives optimised images.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/sites\/:site\/verify",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/sites\/:site\/verify",
                            "variable": [
                                {
                                    "id": "site",
                                    "key": "site",
                                    "value": "example.co.uk",
                                    "description": "The site: its numeric id, its label, or its\n  domain with or without scheme and \"www.\"."
                                }
                            ]
                        },
                        "method": "POST",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Fetches the live page, finds an asset on the delivery hostname, and\nfetches that too.\n\nEvery failure this product has had arrived as a silent success: the\nplugin reports connected, the site returns 200, the conversions table\nfills up, and not one optimised byte reaches a browser - because a DNS\nrecord changed, a certificate lapsed, or a key was rotated and never\npasted back. Nothing on the WordPress side can see that; the evidence\nexists only from outside.\n\n`stage` walks the chain: page, markup, format, asset, delivered. Both\nhostnames are checked to resolve only to public addresses, and the\nconnection is pinned to the address that passed."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"site_id\":7,\"domain\":\"example.co.uk\",\"ok\":true,\"stage\":\"delivered\",\"references\":42,\"sample\":\"https:\/\/images.example.co.uk\/wp-content\/uploads\/2026\/03\/hero.jpg.avif\",\"status\":200,\"content_type\":\"image\/avif\",\"cache\":\"HIT\",\"why\":\"A visitor receives image\/avif from images.example.co.uk.\"}",
                            "name": "delivering"
                        },
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"site_id\":7,\"domain\":\"example.co.uk\",\"ok\":false,\"stage\":\"format\",\"references\":42,\"sample\":null,\"status\":0,\"content_type\":\"\",\"cache\":null,\"why\":\"The page references images.example.co.uk but serves no AVIF or WebP from it, so visitors are still downloading originals.\"}",
                            "name": "not delivering"
                        }
                    ]
                },
                {
                    "name": "Turn the client report link on or off.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/sites\/:site\/report",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/sites\/:site\/report",
                            "variable": [
                                {
                                    "id": "site",
                                    "key": "site",
                                    "value": "example.co.uk",
                                    "description": "The site: its numeric id, its label, or its\n  domain with or without scheme and \"www.\"."
                                }
                            ]
                        },
                        "method": "POST",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\"enabled\":true,\"email\":\"client@example.co.uk\"}"
                        },
                        "description": "Disabling clears the token rather than flipping a flag, so a link that\nhas been revoked cannot come back later by someone toggling the flag the\nother way."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"enabled\":true,\"url\":\"https:\/\/app.peregrinecdn.com\/r\/4f1c8a2e9b7d6350a1c2e4f6b8d0a2c4\",\"email\":\"client@example.co.uk\"}",
                            "name": "enabling"
                        },
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"enabled\":false,\"url\":null}",
                            "name": "disabling - the token is destroyed, not hidden"
                        }
                    ]
                }
            ]
        },
        {
            "name": "Usage",
            "description": "",
            "item": [
                {
                    "name": "Lifetime totals for the account.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/stats",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/stats"
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Reconciled server-side. Do not re-derive savings by summing columns: the\nrollup is keyed by format and holds every image twice."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"images\":54874,\"bytes\":{\"original\":12020896087,\"optimised\":1169791846,\"saved\":10851104241,\"benchmark_modelled\":2432456692},\"percent\":{\"vs_original\":90,\"vs_benchmark\":52},\"benchmark_note\":null,\"human\":{\"original\":\"11.2 GB\",\"optimised\":\"1.1 GB\",\"saved\":\"10.1 GB\"},\"first_day\":\"2026-08-18\",\"last_day\":\"2026-09-15\",\"seconds_saved_at_10mbps\":8680.9}",
                            "name": ""
                        }
                    ]
                },
                {
                    "name": "Daily series across the account.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/trend",
                            "query": [
                                {
                                    "key": "days",
                                    "value": "30",
                                    "description": "Days back to include, 1-365. Defaults to 30.",
                                    "disabled": false
                                }
                            ],
                            "raw": "{{baseUrl}}\/api\/v1\/trend?days=30"
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "One row per day, already deduplicated by format."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "[{\"day\":\"2026-09-14\",\"images\":81,\"original_bytes\":19828439,\"optimised_bytes\":1720154,\"saved\":18108285},{\"day\":\"2026-09-15\",\"images\":183,\"original_bytes\":24211808,\"optimised_bytes\":5494995,\"saved\":18716813}]",
                            "name": ""
                        }
                    ]
                },
                {
                    "name": "Usage over an explicit window.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/usage",
                            "query": [
                                {
                                    "key": "from",
                                    "value": "2026-08-01",
                                    "description": "date Start of the window, inclusive, YYYY-MM-DD. Defaults to 30 days ago.",
                                    "disabled": false
                                },
                                {
                                    "key": "to",
                                    "value": "2026-08-31",
                                    "description": "date End of the window, inclusive, YYYY-MM-DD. Defaults to today.",
                                    "disabled": false
                                },
                                {
                                    "key": "site",
                                    "value": "example.co.uk",
                                    "description": "Restrict to one site.",
                                    "disabled": false
                                }
                            ],
                            "raw": "{{baseUrl}}\/api\/v1\/usage?from=2026-08-01&to=2026-08-31&site=example.co.uk"
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "What an invoice or a monthly client report is built from. The window is\ninclusive at both ends and echoed back, because a report that silently\ndropped its last day would be wrong in the direction nobody checks."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"from\":\"2026-08-01\",\"to\":\"2026-08-31\",\"total\":{\"images\":4223,\"bytes\":{\"original\":229983921,\"optimised\":37648511,\"saved\":192335410,\"benchmark_modelled\":54807310},\"percent\":{\"vs_original\":84,\"vs_benchmark\":31},\"benchmark_note\":null,\"human\":{\"original\":\"219 MB\",\"optimised\":\"35.9 MB\",\"saved\":\"183 MB\"},\"first_day\":\"2026-08-18\",\"last_day\":\"2026-08-31\"},\"sites\":[{\"site_id\":7,\"domain\":\"example.co.uk\",\"label\":\"example-co-uk\",\"images\":4223,\"bytes\":{\"original\":229983921,\"optimised\":37648511,\"saved\":192335410,\"benchmark_modelled\":54807310},\"percent\":{\"vs_original\":84,\"vs_benchmark\":31},\"benchmark_note\":null,\"human\":{\"original\":\"219 MB\",\"optimised\":\"35.9 MB\",\"saved\":\"183 MB\"},\"first_day\":null,\"last_day\":null}]}",
                            "name": ""
                        }
                    ]
                },
                {
                    "name": "Usage for one site.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/sites\/:site\/usage",
                            "query": [
                                {
                                    "key": "from",
                                    "value": "2026-08-01",
                                    "description": "date Inclusive, YYYY-MM-DD.",
                                    "disabled": false
                                },
                                {
                                    "key": "to",
                                    "value": "2026-08-31",
                                    "description": "date Inclusive, YYYY-MM-DD.",
                                    "disabled": false
                                }
                            ],
                            "raw": "{{baseUrl}}\/api\/v1\/sites\/:site\/usage?from=2026-08-01&to=2026-08-31",
                            "variable": [
                                {
                                    "id": "site",
                                    "key": "site",
                                    "value": "example.co.uk",
                                    "description": "The site: its numeric id, its label, or its\n  domain with or without scheme and \"www.\"."
                                }
                            ]
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Lifetime and windowed, plus the daily series."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"site_id\":7,\"domain\":\"example.co.uk\",\"from\":\"2026-08-16\",\"to\":\"2026-09-15\",\"lifetime\":{\"images\":4223,\"bytes\":{\"original\":229983921,\"optimised\":37648511,\"saved\":192335410,\"benchmark_modelled\":54807310},\"percent\":{\"vs_original\":84,\"vs_benchmark\":31},\"benchmark_note\":null,\"human\":{\"original\":\"219 MB\",\"optimised\":\"35.9 MB\",\"saved\":\"183 MB\"},\"first_day\":\"2026-08-18\",\"last_day\":\"2026-09-15\"},\"window\":{\"images\":792,\"bytes\":{\"original\":12939761,\"optimised\":3547457,\"saved\":9392304,\"benchmark_modelled\":6212516},\"percent\":{\"vs_original\":73,\"vs_benchmark\":43},\"benchmark_note\":null,\"human\":{\"original\":\"12.3 MB\",\"optimised\":\"3.4 MB\",\"saved\":\"9.0 MB\"},\"first_day\":\"2026-08-19\",\"last_day\":\"2026-08-19\"},\"days\":[{\"day\":\"2026-08-19\",\"format\":\"avif\",\"images\":792,\"original_bytes\":12939761,\"optimised_bytes\":3547457}]}",
                            "name": ""
                        }
                    ]
                }
            ]
        },
        {
            "name": "Edge",
            "description": "",
            "item": [
                {
                    "name": "Purge URLs from the edge.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/sites\/:site\/invalidate",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/sites\/:site\/invalidate",
                            "variable": [
                                {
                                    "id": "site",
                                    "key": "site",
                                    "value": "example.co.uk",
                                    "description": "The site: its numeric id, its label, or its\n  domain with or without scheme and \"www.\"."
                                }
                            ]
                        },
                        "method": "POST",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\"urls\":[\"https:\\\/\\\/example.co.uk\\\/about\\\/\"]}"
                        },
                        "description": "The plugin never holds a CDN key - that rule is why this exists. It says\nwhat changed; the worker purges, which clears every PoP in one call, and\nre-warms at each PoP the site pays for.\n\nRejections come back in `rejected` rather than being dropped: a\n`{\"queued\": 0}` with a 200 is what weeks of silently-skipped purges look\nlike. More than 500 URLs is refused, not truncated."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"queued\":2,\"rejected\":[]}",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"queued\":1,\"rejected\":[{\"url\":\"not a url\",\"why\":\"not a URL\"}]}",
                            "name": "some URLs were rejected - always read this, a queued of 0 with a 200 is what weeks of silently-skipped purges look like"
                        },
                        {
                            "header": [],
                            "code": 429,
                            "body": "{\"error\":\"queue_full\",\"pending\":20000,\"detail\":\"This site already has 20000 URLs queued and not yet purged. Nothing was added.\"}",
                            "name": "the queue is already full"
                        }
                    ]
                },
                {
                    "name": "Which edge regions a site keeps warm.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/sites\/:site\/warm",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/sites\/:site\/warm",
                            "variable": [
                                {
                                    "id": "site",
                                    "key": "site",
                                    "value": "example.co.uk",
                                    "description": "The site: its numeric id, its label, or its\n  domain with or without scheme and \"www.\"."
                                }
                            ]
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "A CDN caches per point of presence, so a warm run only warms the PoP\nnearest whatever ran it. Measured on a client site warmed from\nFalkenstein: a HIT in about 55ms from Frankfurt, and a MISS at 1.1-1.4s\nfrom London, where the readers actually were.\n\nNot the same axis as the site's storage region, which is one value and\nsays where the origin copies live."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"available\":[\"UK\",\"DE\",\"US-East\",\"US-West\",\"Singapore\",\"Thailand\",\"Australia\",\"Japan\",\"India\",\"Brazil\",\"S-Africa\"],\"allowance\":3,\"regions\":[\"UK\",\"DE\"],\"pops\":[{\"hostname\":\"example.b-cdn.net\",\"region\":\"UK\",\"pop_code\":\"UK1-1486\",\"edge_ip\":\"203.0.113.10\",\"resolved_at\":\"2026-09-14T02:10:58.000000Z\"}],\"job\":{\"site_id\":7,\"status\":\"active\",\"interval_seconds\":604800,\"last_run_at\":\"2026-09-14T02:11:09.000000Z\",\"last_warmed\":812,\"last_errors\":0}}",
                            "name": ""
                        }
                    ]
                },
                {
                    "name": "Choose where a site stays warm.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/sites\/:site\/warm",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/sites\/:site\/warm",
                            "variable": [
                                {
                                    "id": "site",
                                    "key": "site",
                                    "value": "example.co.uk",
                                    "description": "The site: its numeric id, its label, or its\n  domain with or without scheme and \"www.\"."
                                }
                            ]
                        },
                        "method": "PUT",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\"regions\":[\"UK\",\"DE\"],\"interval_seconds\":604800}"
                        },
                        "description": "Billed per location. Asking for more than the account has paid for is\nrefused with 402 rather than silently trimmed - a caller that asked for\nsix and got two would report six, and the only symptom would be\nAustralians finding the site slow."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"regions\":[\"UK\",\"DE\"],\"ignored\":[]}",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 402,
                            "body": "{\"error\":\"over_allowance\",\"asked\":6,\"allowance\":3,\"detail\":\"This account has paid to keep 3 locations warm. Buy more locations, or choose fewer.\"}",
                            "name": "more regions than were paid for"
                        }
                    ]
                },
                {
                    "name": "Ask for a full warm.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/sites\/:site\/warm\/run",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/sites\/:site\/warm\/run",
                            "variable": [
                                {
                                    "id": "site",
                                    "key": "site",
                                    "value": "example.co.uk",
                                    "description": "The site: its numeric id, its label, or its\n  domain with or without scheme and \"www.\"."
                                }
                            ]
                        },
                        "method": "POST",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Expressed as \"this job is overdue\" rather than running anything inline: a\nfull warm is thousands of requests and must not happen inside a request."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"queued\":true,\"detail\":\"Marked overdue. The warm worker picks it up on its next tick; read GET \/v1\/sites\/7\/warm for the result.\"}",
                            "name": ""
                        }
                    ]
                }
            ]
        },
        {
            "name": "Provisioning",
            "description": "",
            "item": [
                {
                    "name": "Build a domain out from nothing.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/provisions",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/provisions"
                        },
                        "method": "POST",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\"domain\":\"example.co.uk\",\"profile\":\"wordpress\",\"region\":\"UK\"}"
                        },
                        "description": "Storage zone, pull zone, hostname, certificate, edge profile and key.\n\nQueued rather than run inline, and that is a security boundary rather\nthan a performance one: provisioning needs the CDN account key, which can\ndelete every zone we run for every client, so it never enters a process\nthat serves web requests. A worker running as the user that owns the key\npicks the row up instead, and the queue doubles as the audit trail.\n\nIt will not reach \"done\" until the hostname resolves, because a\ncertificate cannot be issued before that."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 202,
                            "body": "{\"id\":41,\"status\":\"pending\",\"detail\":\"Queued. The provisioner runs on a tick; poll GET \/v1\/provisions\/41 for status and log. It will not be \\\"done\\\" until the certificate is issued, which needs the hostname to resolve first.\"}",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 409,
                            "body": "{\"error\":\"already_queued\",\"detail\":\"A provision for example.co.uk is already pending.\"}",
                            "name": "one is already in flight for that domain"
                        }
                    ]
                },
                {
                    "name": "Recent provisioning requests.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/provisions",
                            "query": [
                                {
                                    "key": "limit",
                                    "value": "25",
                                    "description": "1-100. Defaults to 25.",
                                    "disabled": false
                                }
                            ],
                            "raw": "{{baseUrl}}\/api\/v1\/provisions?limit=25"
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": ""
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "[{\"id\":41,\"account_id\":42,\"domain\":\"example.co.uk\",\"profile\":\"wordpress\",\"region\":\"UK\",\"status\":\"done\",\"site_id\":7,\"log\":\"storage zone ok \/ pull zone ok \/ hostname ok \/ certificate ok\",\"created_at\":\"2026-09-15 08:20:00\",\"started_at\":\"2026-09-15 08:20:31\",\"finished_at\":\"2026-09-15 08:21:12\"}]",
                            "name": ""
                        }
                    ]
                },
                {
                    "name": "Status and log for one provisioning request.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/provisions\/:id",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/provisions\/:id",
                            "variable": [
                                {
                                    "id": "id",
                                    "key": "id",
                                    "value": 41,
                                    "description": "The request id."
                                }
                            ]
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": ""
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"id\":41,\"account_id\":42,\"domain\":\"example.co.uk\",\"profile\":\"wordpress\",\"region\":\"UK\",\"status\":\"done\",\"site_id\":7,\"log\":\"storage zone ok \/ pull zone ok \/ hostname ok \/ certificate ok\",\"created_at\":\"2026-09-15 08:20:00\",\"started_at\":\"2026-09-15 08:20:31\",\"finished_at\":\"2026-09-15 08:21:12\"}",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 404,
                            "body": "{\"error\":\"not_found\"}",
                            "name": "not on this account"
                        }
                    ]
                }
            ]
        },
        {
            "name": "Analysis",
            "description": "",
            "item": [
                {
                    "name": "Measure any public page.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/analyse",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/analyse"
                        },
                        "method": "POST",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\"url\":\"https:\\\/\\\/example.co.uk\\\/\"}"
                        },
                        "description": "Fetches the page, then re-encodes up to forty of its images - real\nencoding of the real images, not a multiplier. The constant it replaced\ntold a site already running Peregrine that it could save 92%.\n\nWorks on any site, customer or not, so it is the call for qualifying\nprospects and for before-and-after evidence. Slow by nature and throttled\nto 20 a minute per token; a result for the same URL is reused for half an\nhour, which protects the site being analysed as much as it protects us."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"ok\":true,\"url\":\"https:\/\/example.co.uk\/\",\"images_found\":18,\"images_measured\":18,\"bytes\":{\"before\":2660001,\"after\":750884,\"saved\":1909117},\"percent\":72,\"heaviest\":[{\"name\":\"hero.jpg\",\"url\":\"https:\/\/example.co.uk\/wp-content\/uploads\/hero.jpg\",\"before\":486210,\"after\":61204}],\"previews\":[]}",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 502,
                            "body": "{\"error\":\"Could not fetch that page (HTTP 403).\",\"remedies\":[\"Allow PeregrineBot\/1.0 in your firewall\"],\"blocked\":true,\"http\":403}",
                            "name": "the page could not be fetched"
                        }
                    ]
                }
            ]
        },
        {
            "name": "Uptime",
            "description": "",
            "item": [
                {
                    "name": "Uptime monitors and their last result.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/monitors",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/monitors"
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "A monitor answers \"does the site respond\". It would be perfectly happy\nwhile every image on it quietly reverted to a 4 MB original - that is\nwhat the delivery check is for."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "[{\"id\":12,\"name\":\"Storefront\",\"url\":\"https:\/\/example.co.uk\/\",\"method\":\"GET\",\"expect_status\":200,\"interval_seconds\":300,\"status\":\"active\",\"last_checked_at\":\"2026-09-15 08:19:02\",\"last_ok\":1,\"last_ms\":184,\"last_status_code\":200,\"last_error\":null,\"fail_streak\":0,\"cert_expires_at\":\"2026-11-02 11:00:00\"}]",
                            "name": ""
                        }
                    ]
                },
                {
                    "name": "Add a monitor.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/monitors",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/monitors"
                        },
                        "method": "POST",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\"name\":\"Storefront\",\"url\":\"https:\\\/\\\/example.co.uk\\\/\",\"method\":\"GET\",\"expect_status\":200,\"keyword\":\"Add to basket\",\"keyword_absent\":\"Database error\",\"interval_seconds\":300,\"alert_email\":\"ops@example.co.uk\"}"
                        },
                        "description": "The URL must resolve only to public addresses. A monitor is fetched on a\ntimer from two servers and its status code, latency and keyword result\nare readable here, so an unguarded one would be a scheduled scan of our\nown network with the results posted back.\n\n`alert_email` must be the account's own address or someone at the domain\nbeing monitored: an alert is mail from our sending domain carrying a\nsubject you choose, fired when a URL you choose fails.\n\nAt most 100 monitors per account."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 201,
                            "body": "{\"id\":12,\"account_id\":42,\"name\":\"Storefront\",\"url\":\"https:\/\/example.co.uk\/\",\"method\":\"GET\",\"expect_status\":200,\"keyword\":null,\"keyword_absent\":null,\"interval_seconds\":300,\"timeout_seconds\":15,\"confirm_failures\":2,\"alert_email\":\"ops@example.co.uk\",\"ssl_warn_days\":14,\"status\":\"active\",\"created_at\":\"2026-09-15 08:22:00\"}",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 422,
                            "body": "{\"message\":\"The url is not a public address we will fetch: host resolves to a non-public address.\",\"errors\":{\"url\":[\"The url is not a public address we will fetch: host resolves to a non-public address. We only check hostnames that resolve to addresses on the public internet.\"]}}",
                            "name": "the URL points inside a private network"
                        },
                        {
                            "header": [],
                            "code": 422,
                            "body": "{\"error\":\"alert_email_not_allowed\",\"detail\":\"Alerts can go to the account address (ops@acme.example) or to someone at the domain being monitored.\"}",
                            "name": "the alert address is neither yours nor the monitored domain's"
                        }
                    ]
                },
                {
                    "name": "Rename, pause or retime a monitor.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/monitors\/:id",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/monitors\/:id",
                            "variable": [
                                {
                                    "id": "id",
                                    "key": "id",
                                    "value": 12,
                                    "description": "The monitor id."
                                }
                            ]
                        },
                        "method": "PATCH",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\"name\":\"Storefront\",\"status\":\"paused\",\"interval_seconds\":300,\"alert_email\":\"ops@example.co.uk\"}"
                        },
                        "description": ""
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"id\":12,\"account_id\":42,\"name\":\"Storefront\",\"url\":\"https:\/\/example.co.uk\/\",\"method\":\"GET\",\"expect_status\":200,\"interval_seconds\":300,\"status\":\"paused\",\"last_ok\":1,\"last_ms\":184}",
                            "name": ""
                        }
                    ]
                },
                {
                    "name": "Remove a monitor.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/monitors\/:id",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/monitors\/:id",
                            "variable": [
                                {
                                    "id": "id",
                                    "key": "id",
                                    "value": 12,
                                    "description": "The monitor id."
                                }
                            ]
                        },
                        "method": "DELETE",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": ""
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"deleted\":true}",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 404,
                            "body": "{\"error\":\"not_found\"}",
                            "name": "not on this account"
                        }
                    ]
                },
                {
                    "name": "Recent checks for one monitor.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/monitors\/:id\/checks",
                            "query": [
                                {
                                    "key": "limit",
                                    "value": "100",
                                    "description": "1-500. Defaults to 100.",
                                    "disabled": false
                                }
                            ],
                            "raw": "{{baseUrl}}\/api\/v1\/monitors\/:id\/checks?limit=100",
                            "variable": [
                                {
                                    "id": "id",
                                    "key": "id",
                                    "value": 12,
                                    "description": "The monitor id."
                                }
                            ]
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "The raw evidence behind an incident."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "[{\"id\":9044,\"monitor_id\":12,\"checked_at\":\"2026-09-15 08:19:02\",\"ok\":1,\"status_code\":200,\"ms\":184,\"note\":null,\"vantage\":\"primary\"}]",
                            "name": ""
                        }
                    ]
                }
            ]
        },
        {
            "name": "Governance",
            "description": "",
            "item": [
                {
                    "name": "What has been changed through the API, and by which token.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/audit",
                            "query": [
                                {
                                    "key": "limit",
                                    "value": "100",
                                    "description": "1-500. Defaults to 100.",
                                    "disabled": false
                                }
                            ],
                            "raw": "{{baseUrl}}\/api\/v1\/audit?limit=100"
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Write operations only; reads are not recorded, because an audit log that\nincludes them is one nobody reads. Exposed to the account rather than\nkept internal: the point of handing a token to an automation is that\nsomebody can afterwards read what it did, and a trail only the vendor can\nsee does not answer that."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "[{\"id\":318,\"token_name\":\"Acme automation\",\"action\":\"site.invalidate\",\"context\":\"{\\\"site_id\\\":7,\\\"queued\\\":12}\",\"ip\":\"203.0.113.44\",\"created_at\":\"2026-09-15 08:18:40\"}]",
                            "name": ""
                        }
                    ]
                },
                {
                    "name": "List your tokens. Console session only.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/tokens",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/tokens"
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Refused for a bearer token: listing the names and abilities of an\naccount's other credentials hands whoever stole one a map of what to\nsteal next."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "[{\"id\":7,\"name\":\"Acme automation\",\"abilities\":[\"read\",\"purge\",\"analyse\"],\"last_used_at\":\"2026-09-15T08:14:02.000000Z\",\"expires_at\":null,\"created_at\":\"2026-09-01T10:00:00.000000Z\"}]",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 403,
                            "body": "{\"error\":\"session_required\",\"detail\":\"Tokens are managed from a signed-in console session, never by another token. Sign in at https:\/\/app.peregrinecdn.com\/tokens.\"}",
                            "name": "attempted with a token"
                        }
                    ]
                },
                {
                    "name": "Issue a token. Console session only.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/tokens",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/tokens"
                        },
                        "method": "POST",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\"name\":\"Barrie's Claude\",\"abilities\":[\"read\",\"purge\",\"analyse\"],\"expires_in_days\":90}"
                        },
                        "description": "A token that can mint tokens can widen its own abilities, which makes\nevery narrower ability decoration - so this refuses bearer authentication\nand requires a signed-in session."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 201,
                            "body": "{\"id\":8,\"name\":\"Acme automation\",\"abilities\":[\"read\",\"purge\",\"analyse\"],\"expires_at\":null,\"token\":\"8|shown once and never stored\",\"warning\":\"Shown once. Store it where the client that needs it will read it, and nowhere else.\"}",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 403,
                            "body": "{\"error\":\"session_required\",\"detail\":\"Tokens are managed from a signed-in console session, never by another token.\"}",
                            "name": "attempted with a token"
                        }
                    ]
                },
                {
                    "name": "Revoke a token. Console session only.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/tokens\/:id",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/tokens\/:id",
                            "variable": [
                                {
                                    "id": "id",
                                    "key": "id",
                                    "value": 7,
                                    "description": "The token id."
                                }
                            ]
                        },
                        "method": "DELETE",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Refused for a bearer token: otherwise a read-only credential could turn\noff every other credential the account has, including the one an operator\nwould investigate with."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"revoked\":true}",
                            "name": ""
                        }
                    ]
                }
            ]
        },
        {
            "name": "Plugin (site key)",
            "description": "",
            "item": [
                {
                    "name": "Plugin connection check.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/site\/status",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/site\/status"
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Authenticates as a SITE, with the key held by the WordPress plugin - not\nan account token. That key lives in wp_options on a machine we do not\ncontrol, so it is scoped to one site's blast radius: a stolen key purges\nand warms that site and nothing else, cannot read another site, cannot\ntouch DNS or storage, and never reveals a CDN credential.\n\nReturns everything the plugin's settings screen shows, including the\nregion catalogue, so a new region becomes available to every site without\nshipping a plugin update."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"connected\":true,\"site\":{\"label\":\"example-co-uk\",\"domain\":\"example.co.uk\"},\"media\":{\"host\":\"https:\/\/images.example.co.uk\",\"storage_zone\":\"pg-example-co-uk\"},\"warm\":{\"available\":[\"UK\",\"DE\",\"US-East\"],\"regions\":[\"UK\"],\"pops\":[{\"region\":\"UK\",\"pop_code\":\"UK1-1486\",\"edge_ip\":\"203.0.113.10\",\"resolved_at\":\"2026-09-14T02:10:58.000000Z\"}],\"status\":\"active\",\"last_run_at\":\"2026-09-14T02:11:09.000000Z\",\"last_warmed\":812,\"last_errors\":0,\"last_note\":null,\"queue_pending\":0}}",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 401,
                            "body": "{\"error\":\"invalid_key\"}",
                            "name": "the key is wrong, or the site is paused"
                        }
                    ]
                },
                {
                    "name": "Plugin purge report.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/site\/invalidate",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/site\/invalidate"
                        },
                        "method": "POST",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\"urls\":[\"https:\\\/\\\/example.co.uk\\\/about\\\/\"]}"
                        },
                        "description": "The plugin says what changed and nothing more. URLs that are not on the\nsite's own hostname are skipped: without that, a stolen key could queue\npurges for any domain on the platform.\n\nTruncates at 500 rather than refusing, unlike the account route. A plugin\non somebody else's WordPress cannot be told to try again, so quietly\ncapping a runaway sitemap is the least-bad option here."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"queued\":3}",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 401,
                            "body": "{\"error\":\"invalid_key\"}",
                            "name": "the key is wrong, or the site is paused"
                        }
                    ]
                },
                {
                    "name": "Always refused. Warm regions are an account decision.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/site\/warm",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/site\/warm"
                        },
                        "method": "PUT",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Every extra region multiplies the warm: the same page set fetched again\nat another edge, and warming is billed egress. A site holding its own key\ncould otherwise select every region and multiply the bandwidth bill - and\nthe bill lands on us, not on whoever ticked the box.\n\nThe refusal lives here rather than merely being absent from the plugin's\nUI, because a hidden control is not an access control."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 403,
                            "body": "{\"error\":\"not_permitted\",\"detail\":\"Warm regions are set on the account, not from the site. Ask your provider to change them.\"}",
                            "name": ""
                        }
                    ]
                },
                {
                    "name": "Plugin warm request.",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/v1\/site\/warm\/run",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/v1\/site\/warm\/run"
                        },
                        "method": "POST",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Marks the job overdue so the worker picks it up on its next tick.\nClearing running_since as well releases a claim left behind by a run that\ndied, which is otherwise only cleared after an hour."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"queued\":true}",
                            "name": ""
                        },
                        {
                            "header": [],
                            "code": 401,
                            "body": "{\"error\":\"invalid_key\"}",
                            "name": "the key is wrong, or the site is paused"
                        }
                    ]
                }
            ]
        }
    ],
    "auth": {
        "type": "bearer",
        "bearer": [
            {
                "key": "Authorization",
                "type": "string"
            }
        ]
    }
}