{
 "openapi": "3.1.0",
 "info": {
  "title": "Appeals Desk API",
  "version": "2026-08-20",
  "x-dataset-fixity": "sha256-4f67c40fe76468063834465bdf2066bc77a59a41e36573172980c7338f1e1682",
  "description": "UK private parking appeals data: operator→appeals-body routing, deadline computation, and POPLA's own per-operator outcome record (2023-2025, every parse validated against the reports' printed totals). Free with attribution and a link to https://appealsdesk.co.uk. Not legal advice; no outcome guaranteed. Every GET here honours If-None-Match and If-Modified-Since (304)."
 },
 "servers": [
  {
   "url": "https://appealsdesk.co.uk"
  }
 ],
 "x-axion-passport": "https://appealsdesk.co.uk/.well-known/agent-door.json",
 "paths": {
  "/api/answer": {
   "get": {
    "operationId": "getAppealAnswer",
    "summary": "One call: operator + optional dates → route, deadlines, POPLA record, next steps",
    "parameters": [
     {
      "name": "operator",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Operator company name, trading name, or slug (e.g. 'ParkingEye', 'euro-car-parks'). Ambiguous matches return candidates rather than guessing."
     },
     {
      "name": "notice",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "format": "date"
      },
      "description": "Date printed on the parking charge notice, YYYY-MM-DD. Enables computed deadlines."
     },
     {
      "name": "rejected",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "format": "date"
      },
      "description": "Date the operator rejected the appeal. Enables the actual independent-appeal deadline."
     }
    ],
    "responses": {
     "200": {
      "description": "Composed answer — signed; the receipt carries `policy` (what the signature covers)",
      "headers": {
       "ETag": {
        "schema": {
         "type": "string"
        },
        "description": "Strong: \"sha256-<first 32 hex of SHA-256 of the body>\" (on signed bodies, of the body minus the receipt). Send back as If-None-Match for a 304."
       },
       "Last-Modified": {
        "schema": {
         "type": "string"
        },
        "description": "HTTP-date of the newer of the passport's dataset stamp (last_verified.dataset) and the newest item date in /changes.json (return contract v1.1). Send back as If-Modified-Since for a 304."
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      },
      "x-axion-cite": true,
      "x-axion-receipt": true
     },
     "300": {
      "description": "Multiple operator candidates"
     },
     "400": {
      "description": "A date was not YYYY-MM-DD"
     },
     "404": {
      "description": "No operator matched"
     }
    }
   }
  },
  "/api/operators": {
   "get": {
    "operationId": "listOperators",
    "summary": "All 217 UK operators with route and windows",
    "responses": {
     "200": {
      "description": "Operator index",
      "headers": {
       "ETag": {
        "schema": {
         "type": "string"
        },
        "description": "Strong: \"sha256-<first 32 hex of SHA-256 of the body>\" (on signed bodies, of the body minus the receipt). Send back as If-None-Match for a 304."
       },
       "Last-Modified": {
        "schema": {
         "type": "string"
        },
        "description": "HTTP-date of the newer of the passport's dataset stamp (last_verified.dataset) and the newest item date in /changes.json (return contract v1.1). Send back as If-Modified-Since for a 304."
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      },
      "x-axion-cite": true
     }
    }
   }
  },
  "/api/popla-outcomes": {
   "get": {
    "operationId": "getPoplaOutcomes",
    "summary": "POPLA per-operator decided/allowed record with 3-year series",
    "responses": {
     "200": {
      "description": "Outcomes",
      "headers": {
       "ETag": {
        "schema": {
         "type": "string"
        },
        "description": "Strong: \"sha256-<first 32 hex of SHA-256 of the body>\" (on signed bodies, of the body minus the receipt). Send back as If-None-Match for a 304."
       },
       "Last-Modified": {
        "schema": {
         "type": "string"
        },
        "description": "HTTP-date of the newer of the passport's dataset stamp (last_verified.dataset) and the newest item date in /changes.json (return contract v1.1). Send back as If-Modified-Since for a 304."
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      },
      "x-axion-cite": true,
      "x-axion-receipt": true
     }
    }
   }
  },
  "/api/councils": {
   "get": {
    "operationId": "listCouncils",
    "summary": "EHC needs-assessment refusal rates for 153 English councils",
    "responses": {
     "200": {
      "description": "Council index",
      "headers": {
       "ETag": {
        "schema": {
         "type": "string"
        },
        "description": "Strong: \"sha256-<first 32 hex of SHA-256 of the body>\" (on signed bodies, of the body minus the receipt). Send back as If-None-Match for a 304."
       },
       "Last-Modified": {
        "schema": {
         "type": "string"
        },
        "description": "HTTP-date of the newer of the passport's dataset stamp (last_verified.dataset) and the newest item date in /changes.json (return contract v1.1). Send back as If-Modified-Since for a 304."
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      },
      "x-axion-cite": true
     }
    }
   }
  },
  "/changes.json": {
   "get": {
    "operationId": "getChanges",
    "summary": "What moved, when, from where (axion-changes/1); newest first",
    "responses": {
     "200": {
      "description": "Change feed",
      "headers": {
       "ETag": {
        "schema": {
         "type": "string"
        },
        "description": "Strong: \"sha256-<first 32 hex of SHA-256 of the body>\" (on signed bodies, of the body minus the receipt). Send back as If-None-Match for a 304."
       },
       "Last-Modified": {
        "schema": {
         "type": "string"
        },
        "description": "HTTP-date of the newer of the passport's dataset stamp (last_verified.dataset) and the newest item date in /changes.json (return contract v1.1). Send back as If-Modified-Since for a 304."
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     }
    }
   }
  },
  "/changes.atom": {
   "get": {
    "operationId": "getChangesAtom",
    "summary": "The same change feed as Atom 1.0",
    "responses": {
     "200": {
      "description": "Atom feed",
      "headers": {
       "ETag": {
        "schema": {
         "type": "string"
        },
        "description": "Strong: \"sha256-<first 32 hex of SHA-256 of the body>\" (on signed bodies, of the body minus the receipt). Send back as If-None-Match for a 304."
       },
       "Last-Modified": {
        "schema": {
         "type": "string"
        },
        "description": "HTTP-date of the newer of the passport's dataset stamp (last_verified.dataset) and the newest item date in /changes.json (return contract v1.1). Send back as If-Modified-Since for a 304."
       }
      },
      "content": {
       "application/atom+xml": {
        "schema": {
         "type": "string"
        }
       }
      }
     }
    }
   }
  },
  "/llms.txt": {
   "get": {
    "operationId": "getLlmsTxt",
    "summary": "The whole site as one plain-text document for answer engines",
    "responses": {
     "200": {
      "description": "Plain text",
      "headers": {
       "ETag": {
        "schema": {
         "type": "string"
        },
        "description": "Strong: \"sha256-<first 32 hex of SHA-256 of the body>\" (on signed bodies, of the body minus the receipt). Send back as If-None-Match for a 304."
       },
       "Last-Modified": {
        "schema": {
         "type": "string"
        },
        "description": "HTTP-date of the newer of the passport's dataset stamp (last_verified.dataset) and the newest item date in /changes.json (return contract v1.1). Send back as If-Modified-Since for a 304."
       }
      },
      "content": {
       "text/plain": {
        "schema": {
         "type": "string"
        }
       }
      }
     }
    }
   }
  },
  "/api/bulk": {
   "get": {
    "summary": "Priced door: Every operator with its appeal route and windows, POPLA's per-operator decided and allowed record, English councils' EHC needs-assessment refusal rates and the court electronic-bundle rulebooks, in one response (GBP 9 per full-dataset pull)",
    "responses": {
     "200": {
      "description": "The record, when a key holding credit or a free allowance is presented as Authorization: Bearer <key>"
     },
     "402": {
      "description": "x402 v2 PaymentRequired envelope; accepts[0].payTo is a Stripe Payment Link"
     }
    },
    "x-axion-priced": true,
    "x-payment-info": {
     "intent": "charge",
     "method": "stripe",
     "amount": 9,
     "currency": "GBP",
     "available": false,
     "description": "Every operator with its appeal route and windows, POPLA's per-operator decided and allowed record, English councils' EHC needs-assessment refusal rates and the court electronic-bundle rulebooks, in one response; GBP 9 per full-dataset pull, not yet payable: the rail is mounted and this price is the price that will apply, but no payment link is configured, so the 402 on this path carries an empty accepts list and names no payTo. The price list and the guarantees are readable now at https://appealsdesk.co.uk/api/key/checkout"
    },
    "security": [
     {
      "axionKey": []
     },
     {
      "axionOAuth": [
       "read",
       "bulk",
       "changes"
      ]
     }
    ]
   }
  },
  "/api/changes": {
   "get": {
    "summary": "Priced door: Which records moved since a date (GBP 0.02 per call)",
    "responses": {
     "200": {
      "description": "The record, when a key holding credit or a free allowance is presented as Authorization: Bearer <key>"
     },
     "402": {
      "description": "x402 v2 PaymentRequired envelope; accepts[0].payTo is a Stripe Payment Link"
     }
    },
    "x-axion-priced": true,
    "x-payment-info": {
     "intent": "charge",
     "method": "stripe",
     "amount": 0.02,
     "currency": "GBP",
     "available": false,
     "description": "Which records moved since a date; GBP 0.02 per call, not yet payable: the rail is mounted and this price is the price that will apply, but no payment link is configured, so the 402 on this path carries an empty accepts list and names no payTo. The price list and the guarantees are readable now at https://appealsdesk.co.uk/api/key/checkout"
    },
    "security": [
     {
      "axionKey": []
     },
     {
      "axionOAuth": [
       "read",
       "bulk",
       "changes"
      ]
     }
    ]
   }
  }
 },
 "x-service-info": {
  "name": "Appeals Desk",
  "provider": "Axion Labs",
  "categories": [
   "data",
   "parking-appeals",
   "uk-public-records",
   "popla"
  ],
  "contact": "hello@getaxionlabs.com",
  "auth": "https://appealsdesk.co.uk/auth.md",
  "a2a": "https://appealsdesk.co.uk/.well-known/agent-card.json"
 },
 "components": {
  "securitySchemes": {
   "axionKey": {
    "type": "http",
    "scheme": "bearer",
    "description": "An API key from https://appealsdesk.co.uk/agent/identity or https://appealsdesk.co.uk/api/key/free. Every free operation answers without one."
   },
   "axionOAuth": {
    "type": "oauth2",
    "flows": {
     "clientCredentials": {
      "tokenUrl": "https://appealsdesk.co.uk/oauth/token",
      "scopes": {
       "read": "every free endpoint (they all answer without a token too)",
       "bulk": "/api/bulk, the whole corpus in one call, against credit or the free allowance",
       "changes": "/api/changes, what moved since a date, against credit or the free allowance"
      }
     }
    }
   }
  }
 }
}