{"openapi":"3.1.0","info":{"title":"Duesday API","version":"1.0.0","summary":"Work out what a consumer is owed, anywhere in the world, and assemble the letter that claims it.","description":"Claims agencies take 25–50% of your money. We give you the exact rule, the exact letter, and the exact address — free, and you keep 100%.\n\nThe API is free, unauthenticated, and rate-limited per IP. It stores nothing: a claim is\nevaluated and discarded within the request. There is no account system and no claim history\non the server, by design.\n\n**What this is not.** It is not legal advice, and it does not act as anybody's agent. It returns\nwhat the law provides for, with citations, plus a document the *user* sends themselves in their\nown name. Any integration must preserve that framing.","license":{"name":"AGPL-3.0-or-later","identifier":"AGPL-3.0-or-later"},"contact":{"name":"Duesday support","url":"https://github.com/Soham109/duesday"}},"servers":[{"url":"https://duesday.sohamaggarwal.com/api/v1","description":"Production"}],"tags":[{"name":"evaluation","description":"Work out what is owed."},{"name":"letters","description":"Assemble a document the claimant sends themselves."},{"name":"catalogue","description":"What regimes and jurisdictions are covered."},{"name":"reference","description":"Airports, distances and lookup data."},{"name":"lookup","description":"Reference search over the local dataset, and optional flight-status lookup. Every field these fill is a suggestion the user may overwrite."}],"paths":{"/health":{"get":{"tags":["catalogue"],"summary":"Liveness and coverage fingerprint","description":"Includes rule and template counts so a deploy that silently drops a jurisdiction is detectable.","responses":{"200":{"description":"Service is up","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/evaluate":{"post":{"tags":["evaluation"],"summary":"Evaluate a claim against every applicable jurisdiction","description":"Runs every rule module registered for the claim category and returns each verdict, ranked,\nwith the reasoning trace intact.\n\nMore than one regime frequently applies to the same facts — a Paris-to-New-York delay is\ncovered by both EU261 and the Montreal Convention. The response reconciles them in `groups`\nrather than naively summing: `bestTotal` is what is realistically recoverable, and\n`theoreticalMax` is the arithmetic sum, shown only as an upper bound.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluateRequest"}}}},"responses":{"200":{"description":"Evaluation complete","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluateResponse"}}}},"422":{"$ref":"#/components/responses/ValidationProblem"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/rules":{"get":{"tags":["catalogue"],"summary":"List every regime the engine knows about","parameters":[{"name":"category","in":"query","schema":{"$ref":"#/components/schemas/ClaimCategory"}},{"name":"jurisdiction","in":"query","schema":{"type":"string"},"example":"US-CA"},{"name":"confidence","in":"query","description":"Minimum confidence to include.","schema":{"type":"string","enum":["high","medium","low"]}}],"responses":{"200":{"description":"Rule catalogue","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/rules/{id}":{"get":{"tags":["catalogue"],"summary":"One regime, with its explainer and full sourcing","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"air.eu261"}],"responses":{"200":{"description":"Rule metadata","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/jurisdictions":{"get":{"tags":["catalogue"],"summary":"Coverage map across every category","responses":{"200":{"description":"Coverage","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/dashboard/library/templates":{"get":{"tags":["letters"],"summary":"Every document the product can assemble","parameters":[{"name":"ruleId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Template catalogue","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/dashboard/library/render":{"post":{"tags":["letters"],"summary":"Assemble a demand letter","description":"The evaluation is recomputed server-side from the claim facts; a client-supplied\n`Evaluation` is not accepted. This guarantees the figure in the letter is one the engine\nstands behind, rather than an arbitrary number wearing our citations.\n\nEvery rendered document carries the not-legal-advice disclaimer. There is no option to\nsuppress it, and integrations must not strip it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderLetterRequest"}}}},"responses":{"200":{"description":"Rendered document in the requested formats","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationProblem"}}}},"/airports/{iata}":{"get":{"tags":["reference"],"summary":"Airport lookup by IATA code","parameters":[{"name":"iata","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z]{3}$"}}],"responses":{"200":{"description":"Airport","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/distance":{"get":{"tags":["reference"],"summary":"Great-circle distance between two airports","description":"The figure the compensation bands are calculated from — first departure to final destination, per CJEU C-559/16 (Bossen).","parameters":[{"name":"from","in":"query","required":true,"schema":{"type":"string"},"example":"LHR"},{"name":"to","in":"query","required":true,"schema":{"type":"string"},"example":"JFK"}],"responses":{"200":{"description":"Distance","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/lookup/airports":{"get":{"tags":["lookup"],"summary":"Ranked airport search","description":"Fuzzy search over IATA code, ICAO code, city, airport name and IATA metropolitan codes —\n`LON` returns the six London fields in order, `NYC` the three New York ones.\n\nAnswered entirely from the local dataset. **No outbound request is made**, so a passenger\ntyping their route into a picker discloses it to nobody, and the endpoint is safe to call on\nevery keystroke.\n\nRanking: exact IATA, then exact ICAO, then metropolitan code, then exact city, then prefix\nmatches, then substring matches. Ties break on a coarse prominence tier, so `Paris` lists\nCharles de Gaulle above Beauvais.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2},"example":"heathrow"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":25,"default":8}}],"responses":{"200":{"description":"Ranked matches","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AirportSearchResponse"}}}},"400":{"$ref":"#/components/responses/ValidationProblem"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/lookup/airlines":{"get":{"tags":["lookup"],"summary":"Ranked carrier search","description":"Search over IATA designator, ICAO designator and carrier name. Local dataset only.\n\n`euCarrier` and `ukCarrier` are derived from the state that granted the operating licence, not\nfrom the brand or the fleet registration — that is the test in Art. 3(1)(b) of Regulation\n261/2004, and it decides whether a third-country departure into the EU is in scope at all.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1},"example":"ryanair"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":25,"default":8}}],"responses":{"200":{"description":"Ranked matches","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AirlineSearchResponse"}}}},"400":{"$ref":"#/components/responses/ValidationProblem"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/lookup/distance":{"get":{"tags":["lookup"],"summary":"Great-circle distance, with both endpoints resolved","description":"The wizard-facing superset of `/distance`, which remains the canonical endpoint. Both compute\nthe figure through the same function, so they cannot disagree.\n\nThis one additionally accepts ICAO codes and IATA metropolitan codes, and returns each end's\ntimezone and EU261/UK261 territorial scope — which a claim form needs anyway and would\notherwise cost two further round trips.","parameters":[{"name":"from","in":"query","required":true,"schema":{"type":"string"},"example":"LON"},{"name":"to","in":"query","required":true,"schema":{"type":"string"},"example":"JFK"}],"responses":{"200":{"description":"Distance and both endpoints","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/ValidationProblem"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/flights":{"get":{"tags":["lookup"],"summary":"Whether flight lookup is configured in this deployment","description":"Returns `configured: false` with a 200, not an error — a deployment with no flight-data\nprovider is a supported state and the default for a fresh clone. Call this before offering a\n\"look it up\" button, rather than offering one that always fails.","responses":{"200":{"description":"Capability report","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/flights/{flight}":{"get":{"tags":["lookup"],"summary":"Flight status by flight number and date","description":"Resolves a flight to its scheduled and actual times in UTC, its airports, its aircraft, its\noperating carrier and a computed arrival delay.\n\n**What is sent upstream.** The flight number, the date, and — only for providers that cannot\nsearch any other way — the airport codes. Nothing identifying the claimant is in scope in the\nhandler, let alone forwarded: a provider's only argument is a type with three fields, none of\nwhich can hold a name, an address or a booking reference. Nothing is persisted; the cache is\nin-memory, keyed by the flight and the date, and never by the caller.\n\n**Operating versus marketing carrier.** Modelled separately and deliberately. EU261 Art. 2(b)\nputs every obligation on the *operating* air carrier, and a demand sent to the airline whose\ncode is on the ticket is routinely refused on that ground alone. Where a provider does not name\nthe operator directly, it may be derived from the ATC callsign — and where it is, `limitations`\nsays so.\n\n**Honesty requirements on any consumer of this response.** Every value here is a suggestion.\nIt must be labelled as looked up, attributed to `source`, and left editable. Never substitute a\nprovider's delay figure for the user's own: where they disagree, show both. A claim is the\nclaimant's statement of fact and they were on the aeroplane.\n\n**503 is a normal answer.** With no provider configured — the default for a fresh clone — this\nreturns a 503 problem detail saying so and pointing at manual entry. The whole claim flow works\nwith this endpoint switched off.","parameters":[{"name":"flight","in":"path","required":true,"description":"Carrier designator and number, IATA or ICAO, e.g. `BA117` or `BAW117`.","schema":{"type":"string","pattern":"^[A-Za-z0-9]{2,3}\\d{1,4}$"},"example":"BA117"},{"name":"date","in":"query","required":true,"description":"Local calendar date of scheduled departure at the origin — the date on the boarding pass.","schema":{"type":"string","format":"date"},"example":"2026-07-14"},{"name":"from","in":"query","description":"Origin airport, if already known. A hint: providers that search by flight number ignore it, and ADS-B providers cannot answer without it.","schema":{"type":"string"}},{"name":"to","in":"query","description":"Destination airport, same role as `from`.","schema":{"type":"string"}}],"responses":{"200":{"description":"Flight resolved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlightStatusResponse"}}}},"400":{"$ref":"#/components/responses/ValidationProblem"},"404":{"description":"No configured source had this flight. Not evidence the flight did not run.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"No provider is configured, or none covers that date. Enter the details by hand.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}},"components":{"responses":{"ValidationProblem":{"description":"The request did not match the expected shape","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"NotFound":{"description":"No such resource","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"RateLimited":{"description":"Too many requests","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"schemas":{"Problem":{"type":"object","description":"RFC 9457 problem detail.","required":["type","title","status"],"properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"},"errors":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"},"code":{"type":"string"}}}}}},"Money":{"type":"object","description":"An integer count of minor units plus an ISO 4217 code. `{ \"amount\": 60000, \"currency\": \"EUR\" }` is EUR 600.00. Never a float — rounding a statutory award through IEEE-754 misstates it.","required":["amount","currency"],"properties":{"amount":{"type":"integer"},"currency":{"type":"string","pattern":"^[A-Z]{3}$"}}},"MoneyRange":{"type":"object","required":["min","max"],"properties":{"min":{"$ref":"#/components/schemas/Money"},"max":{"$ref":"#/components/schemas/Money"},"exact":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Present when the rule yields a single deterministic figure."}}},"ClaimCategory":{"type":"string","enum":["flight-disruption","baggage","rental-deposit","unclaimed-property","card-billing","subscription","unwanted-calls","rail-delay","parcel-delay","medical-billing","data-rights","class-action","hidden-fees"]},"Verdict":{"type":"string","enum":["eligible","ineligible","out-of-scope","needs-more-info","time-barred"]},"TraceStep":{"type":"object","description":"One recorded decision step. The trace is why a figure can be walked back to the check that produced it.","required":["code","label","outcome","detail"],"properties":{"code":{"type":"string"},"label":{"type":"string"},"outcome":{"type":"string","enum":["pass","fail","inconclusive","info"]},"detail":{"type":"string"},"observed":{"type":"object","additionalProperties":true},"citation":{"$ref":"#/components/schemas/Citation"}}},"Citation":{"type":"object","required":["label","instrument"],"properties":{"label":{"type":"string"},"instrument":{"type":"string"},"pinpoint":{"type":"string"},"url":{"type":"string","format":"uri"},"verifiedOn":{"type":"string","format":"date"}}},"Evaluation":{"type":"object","required":["ruleId","ruleName","jurisdiction","category","verdict","confidence"],"properties":{"ruleId":{"type":"string"},"ruleName":{"type":"string"},"category":{"$ref":"#/components/schemas/ClaimCategory"},"verdict":{"$ref":"#/components/schemas/Verdict"},"confidence":{"type":"string","enum":["high","medium","low"]},"award":{"$ref":"#/components/schemas/MoneyRange"},"components":{"type":"array","items":{"type":"object"}},"remedies":{"type":"array","items":{"type":"object"}},"deadlines":{"type":"array","items":{"type":"object"}},"escalation":{"type":"array","items":{"type":"object"}},"trace":{"type":"array","items":{"$ref":"#/components/schemas/TraceStep"}},"citations":{"type":"array","items":{"$ref":"#/components/schemas/Citation"}},"missing":{"type":"array","items":{"type":"object"}},"letterTemplates":{"type":"array","items":{"type":"string"}},"anticipatedDefences":{"type":"array","items":{"type":"object"}},"warnings":{"type":"array","items":{"type":"string"}}}},"EvaluateRequest":{"type":"object","required":["claim"],"properties":{"claim":{"type":"object","description":"A discriminated union keyed on `category`. See /rules for what each category supports."},"asOf":{"type":"string","format":"date","description":"Evaluation date. Every deadline is computed from this, so passing it explicitly makes results reproducible."},"language":{"type":"string"},"displayCurrency":{"type":"string","pattern":"^[A-Z]{3}$"},"includeLowConfidence":{"type":"boolean","description":"Include regimes we could not verify from primary sources. Off by default; these always carry a warning."},"includeOutOfScope":{"type":"boolean"}}},"EvaluateResponse":{"type":"object","properties":{"asOf":{"type":"string","format":"date"},"evaluations":{"type":"array","items":{"$ref":"#/components/schemas/Evaluation"}},"actionable":{"type":"array","items":{"$ref":"#/components/schemas/Evaluation"}},"bestTotal":{"$ref":"#/components/schemas/MoneyRange"},"theoreticalMax":{"$ref":"#/components/schemas/MoneyRange"},"groups":{"type":"array","items":{"type":"object"}},"missing":{"type":"array","items":{"type":"object"}},"urgentDeadline":{"type":"object"},"warnings":{"type":"array","items":{"type":"string"}},"stats":{"type":"object"},"disclaimer":{"type":"string"}}},"RenderLetterRequest":{"type":"object","required":["claim","templateId","ruleId","claimant","recipient"],"properties":{"claim":{"type":"object"},"templateId":{"type":"string","example":"flight.initial-demand"},"ruleId":{"type":"string","example":"air.eu261"},"claimant":{"type":"object"},"recipient":{"type":"object"},"language":{"type":"string"},"asOf":{"type":"string","format":"date"},"responseDays":{"type":"integer","minimum":1,"maximum":180},"additionalFacts":{"type":"string"},"rejectionText":{"type":"string","description":"What the counterparty said when they refused, for rebuttal templates."},"formats":{"type":"array","items":{"type":"string","enum":["text","markdown","html","blocks"]}}}},"Health":{"type":"object","properties":{"status":{"type":"string"},"service":{"type":"string"},"apiVersion":{"type":"string"},"coverage":{"type":"object"}}},"AirportMatch":{"type":"object","required":["iata","icao","name","city","country","lat","lon","timeZone","eu261","uk261"],"properties":{"iata":{"type":"string","minLength":3,"maxLength":3},"icao":{"type":"string","minLength":4,"maxLength":4},"name":{"type":"string"},"city":{"type":"string"},"country":{"type":"string","description":"ISO 3166-1 alpha-2. EU outermost regions carry their own code — Réunion is `RE`, not `FR` — so a naive `country === \"FR\"` test drops them from EU261 scope."},"lat":{"type":"number"},"lon":{"type":"number"},"timeZone":{"type":"string","description":"IANA identifier, for rendering local time. Never used for arithmetic."},"eu261":{"type":"boolean","description":"Inside Regulation (EC) 261/2004 territorial scope."},"uk261":{"type":"boolean"},"score":{"type":"number","description":"Relevance. Exposed so a caller can see why an order came out as it did."},"matchedOn":{"type":"string","enum":["iata","icao","name","city","alias"]}}},"AirlineMatch":{"type":"object","required":["iata","icao","name","country","euCarrier","ukCarrier"],"properties":{"iata":{"type":"string"},"icao":{"type":"string"},"name":{"type":"string","description":"The licensed legal entity, which is who a letter is addressed to — not the trading brand."},"country":{"type":"string","description":"State that granted the operating licence, not the headquarters."},"euCarrier":{"type":"boolean","description":"A \"Community carrier\" under Art. 2(c) of Regulation 261/2004."},"ukCarrier":{"type":"boolean"},"hasClaimChannel":{"type":"boolean","description":"We carry a complaint address or form for this carrier."},"note":{"type":"string"},"score":{"type":"number"},"matchedOn":{"type":"string","enum":["iata","icao","name","city","alias"]}}},"AirportSearchResponse":{"type":"object","properties":{"query":{"type":"string"},"count":{"type":"integer"},"results":{"type":"array","items":{"$ref":"#/components/schemas/AirportMatch"}},"source":{"type":"string","enum":["local"]},"note":{"type":"string"}}},"AirlineSearchResponse":{"type":"object","properties":{"query":{"type":"string"},"count":{"type":"integer"},"results":{"type":"array","items":{"$ref":"#/components/schemas/AirlineMatch"}},"source":{"type":"string","enum":["local"]},"note":{"type":"string"}}},"LookupSource":{"type":"object","description":"Where a looked-up value came from. Must be rendered alongside the value it explains.","required":["provider","providerName","attribution","providerUrl","retrievedAt","cached"],"properties":{"provider":{"type":"string","example":"aerodatabox"},"providerName":{"type":"string"},"attribution":{"type":"string","description":"Display this next to the data. Several providers require it."},"providerUrl":{"type":"string","format":"uri"},"retrievedAt":{"type":"string","format":"date-time"},"cached":{"type":"boolean","description":"Served from an in-memory TTL cache keyed by the flight, never by the caller."}}},"FlightEndpoint":{"type":"object","properties":{"iata":{"type":"string"},"icao":{"type":"string"},"name":{"type":"string"},"terminal":{"type":"string"},"scheduledUtc":{"type":"string","format":"date-time","description":"UTC, ISO 8601, always ending in `Z`. Absent for ADS-B sources, which observe but do not plan."},"actualUtc":{"type":"string","format":"date-time"},"actualIsEstimate":{"type":"boolean","description":"True when `actualUtc` is a projection rather than an observation. A claim resting on an estimate is one an airline can rebut with its own record."},"timeZone":{"type":"string"}}},"FlightCarrier":{"type":"object","properties":{"iata":{"type":"string"},"icao":{"type":"string"},"name":{"type":"string"}}},"FlightStatus":{"type":"object","required":["flightNumber","date","departure","arrival","codeshare","status","source","limitations"],"properties":{"flightNumber":{"type":"string","example":"BA117"},"date":{"type":"string","format":"date"},"departure":{"$ref":"#/components/schemas/FlightEndpoint"},"arrival":{"$ref":"#/components/schemas/FlightEndpoint"},"marketingCarrier":{"allOf":[{"$ref":"#/components/schemas/FlightCarrier"}],"description":"Whose code is on the ticket."},"operatingCarrier":{"allOf":[{"$ref":"#/components/schemas/FlightCarrier"}],"description":"Who actually flew it. Under EU261 Art. 2(b) this is the carrier a claim must be addressed to."},"codeshare":{"type":"boolean","description":"`false` from a provider that models no codeshare data means \"not known to be\", not \"is not\" — check `limitations`."},"aircraftType":{"type":"string"},"registration":{"type":"string"},"status":{"type":"string","enum":["scheduled","active","landed","cancelled","diverted","unknown"]},"arrivalDelayMinutes":{"type":"integer","description":"Difference of two UTC instants, positive for late, truncated toward zero so a near-threshold delay is never overstated. Absent when either end is unknown."},"departureDelayMinutes":{"type":"integer"},"source":{"$ref":"#/components/schemas/LookupSource"},"limitations":{"type":"array","items":{"type":"string"},"description":"What this particular answer cannot tell you. Render verbatim; an empty array is not a claim of certainty."}}},"FlightStatusResponse":{"type":"object","properties":{"flight":{"$ref":"#/components/schemas/FlightStatus"},"prefill":{"type":"object","description":"The same facts shaped for a claim form. `provenance` maps each filled field to the source that supplied it; rendering the values without it misrepresents a data feed as the claimant's own testimony. `stillNeeded` lists what no feed can answer."},"attribution":{"type":"string"},"providers":{"type":"array","description":"Every provider consulted, skipped or failed, with the reason. The reasons are the useful part.","items":{"type":"object"}},"editable":{"type":"boolean","enum":[true]},"note":{"type":"string"}}}}}}