The check, as an endpoint
POST /api/v1/check runs the same evaluator as the web check: answers in, one finding per duty out. Unauthenticated, CORS-open, JSON both ways. Built for scripts and AI agents acting for landlords. The check on the website is just another client of this endpoint.
"draftRuleSet" is true while any finding in the response comes from a rule that has not finished being verified, and every finding has its own verified flag. Treat a finding with "verified": false as tracking data, not legal fact, and follow each finding’s sourceUrl.Request
Yes/no fields take "yes" / "no"; most also accept "unsure" or null — an honest “don’t know” comes back as an ask, never silently green. epcRating / epcExpiryDate and the done-ness pair (depositProtected, rtrChecked) are optional and default to null.
curl -s -X POST https://www.datestone.io/api/v1/check \
-H 'Content-Type: application/json' \
-d '{
"postcode": "LS1 3AD",
"propertyType": "house",
"hasGasAppliances": "yes",
"hasTenancy": "yes",
"letting": "rooms",
"occupantsCount": 6,
"householdsCount": 6,
"tenancyTiming": "both",
"infoSheetServed": "yes",
"writtenStatementServed": "unsure",
"depositTaken": "yes",
"depositProtected": "not_yet",
"rtrChecked": "yes",
"epcRating": null,
"epcExpiryDate": null
}'The fields
The check asks who lives there and how it is let, and works out whether the home is a house in multiple occupation (HMO) from that and the head-count, as the law does. It asks no start date: when the tenancies began is one answer for any number of them.
propertyType:house,flatorunsure.hasTenancy: is anyone living there now (yes/no).letting: who lives there now, or for an empty home how it will be let:one_household(a person, a couple or a family on one agreement),sharers(people who aren’t all one family, on one joint agreement),rooms(room by room, each on their own agreement),lodgers(in the home the landlord lives in, sharing their kitchen or bathroom),company(a company that sublets it or puts its staff there) orunsure(“Not decided yet” for an empty home). Lodgers needhasTenancyyes.occupantsCount,householdsCount: whole numbers ornull. Not needed forone_household. With lodgers, count the landlord and their family too.peopleBesidesOwner(two_or_fewer,three_or_moreorunsure) andownerLongInterest(the freehold or a lease of more than 21 years): lodgers only.purposeBuiltBlockFlat: a flat in a block built as flats, with 3 or more flats in it.tenancyTiming:before1 May 2026,after(on or after),both(some of each: only withroomsorunsure) orunsure.bothcan owe the information sheet and the written statement at once, each by its own answer (infoSheetServed,writtenStatementServed).depositProtected:yes,no,unsureornot_yet: paid in the last 30 days, so still “Needed”, never “Out of date”.- A
companylet has no tenancy rows: the response’slettingNotesays why, and whose right to rent checks they are.
The fields of the check’s earlier questions are still taken, read as they always were: isHmo, propertyType: "hmo", tenancyStartDate and preRra, in a request without letting. A request that mixes the two shapes, or contradicts itself, is refused with a 400 that says which answers clash.
Response
outOfScope — API callers must read this. Non-null (the country name, e.g. "Wales") when the postcode is outside England: findings and the exposure figures are empty because we don’t cover that country yet, NOT because the property is fine.
Each finding’s word is the state, in the product’s one vocabulary, and stateLabel is what a landlord reads for it: need_now (“Out of date”), need (“Due soon” over a date ahead, “Needed” without one: it applies here, and its date or record is not in yet), might_need (“Ask the council”: licensing only the council can confirm), one_answer (“Not known yet”: whether it applies turns on one fact the landlord hasn’t given; never a missing date alone), covered (“In date” over a date ahead, “Done” without one; a duty your answers settled, such as a deposit you say is protected or a right-to-rent check you say was done, reads “Done, based on your answers” whatever date it keeps), watching (“Not law yet”) or good_practice (“Good practice”). The word never moves with the date and the label can, so group by word and print stateLabel. rag and cantTell are kept for API-shape stability. cantTell: true means the answers couldn’t settle the finding; read the state from word. stateNote, when present, is the sentence after the label that says why it reads so: on need without a date, that every rental like this one needs it; on one_answer, the question it turns on. needsDate: true marks a duty that applies whose certificate date the check doesn’t ask for. Both are additive (7 Oct 2026). need_now (“Out of date”) comes only from an answer you sent as "no" (deposit protected, right to rent, the information sheet, the written statement) or from the epcExpiryDate you sent: a date the check works out itself, such as a tenancy’s start, never makes one (7 Oct 2026). paper: true marks a certificate or licence the home holds (additive, 7 Oct 2026).
Money is integer pence. penaltyMaxPence: null means the penalty is unlimited or scales with the case — such findings are counted in unquantifiableCount, never summed as zero. exposureFirstPence is what first breaches of the out-of-date findings can cost (a tiered rule’s first tier); exposureMaxPence sums the higher tiers. Each finding carries both, as penaltyFirstPence and penaltyMaxPence, with its penaltyPhrase.
ruleSetUpdatedAt is when any rule last moved (verified or edited) — refresh cached results when it changes. continueUrl turns this check into a prefilled signup: send a person there and their first property form arrives with these answers already in place.
{
"version": "v1",
"draftRuleSet": true,
"ruleSetUpdatedAt": "2026-08-12T09:30:00.000Z",
"disclaimer": "Tracking data, not legal advice. …",
"outOfScope": null,
"councilName": "Leeds",
"exposureMaxPence": 9700000,
"exposureFirstPence": 3700000,
"unquantifiableCount": 2,
"findings": [
{
"templateKey": "deposit_protection",
"name": "Deposit protection + prescribed information",
"rag": "amber",
"cantTell": true,
"word": "need",
"stateLabel": "Needed",
"stateNote": "Every tenancy like yours needs this.",
"actionText": "Confirm the deposit is protected in a government-approved scheme…",
"penaltyText": "Court can order 1–3× the deposit to the tenant…",
"penaltyMaxPence": null,
"advisory": false,
"sourceUrl": "https://www.gov.uk/deposit-protection-schemes-and-landlords",
"verified": false,
"aiCheckedAt": "2026-08-10T07:00:00.000Z",
"lastVerifiedAt": null,
"dueDateIso": null
}
],
"lettingNote": null,
"continueUrl": "https://www.datestone.io/api/v1/continue?token=…"
}Limits & conduct
- 20 requests per minute per IP; you’ll get a 429 with Retry-After.
- A generous daily cap per origin sits behind that — agents checking real properties will never see it; bulk dumps will.
- No key needed today. If that changes, v1 stays available and documented.
- Responses are per-request computations — cache on your side if you’re checking the same answers repeatedly, and refresh when
ruleSetUpdatedAtmoves.
For connected apps
Five calls for an app that manages properties and wants Datestone to track what each one owes the law. They take an API key issued by Datestone (Authorization: Bearer <your key>), and a key only ever reaches the properties in its own account. Datestone takes compliance facts only: no tenant, rent or contact details, ever.
POST /api/v1/properties— register a property. Returns itsrefand theopenQuestionsstill open. The same address twice returns the same ref and changes nothing; each flat is its own property, whichever address line names it.PATCH /api/v1/properties/{ref}— set or correct the facts. Only the fields you send change; anything else is refused whole.GET /api/v1/properties/{ref}/obligations— what the property owes, one row per duty.POST /api/v1/properties/{ref}/certificates— this got done, here is the date.DELETE /api/v1/properties/{ref}/certificates/{type}— take a filed date back.
curl -s -X POST https://www.datestone.io/api/v1/properties \
-H 'Authorization: Bearer <your key>' \
-H 'Content-Type: application/json' \
-d '{
"addressLine1": "Leeds Town Hall, The Headrow",
"postcode": "LS1 3AD",
"propertyType": "house",
"bedrooms": 3
}'Facts
hasGasAppliances:true,false, ornullfor not known. Leave it out and it is not known — the gas safety duty stays on the property as an open question. Onlyfalseswitches it off.isHmo:true,false, ornullfor not known, the same way. Leave it out and the HMO licence rows stay as open questions. Afalseis refused, on registration and on update, where 3 or more people or 2 or more households live with the other count at the test or missing, or where the home is let room by room or to lodgers and the counts are missing, unlesshmoExceptiongives a reason the law states.hmoException:two_people(only two people live there, forming two households) orresident_owner(the owner lives there with members of their household and no more than two other people). SendownerLongInterest: truewithresident_owner: it answers whether the owner holds the freehold or a lease of more than 21 years, the reason is refused without it, and it is never stored. Kept only besideisHmo: false, and cleared when the head-counts change unless it is sent again.lettingStructure:whole,rooms,lodgers(the owner, a person, lives there as their main home and shares a kitchen, bathroom, toilet or living room with them: no deposit protection, Renters’ Rights Act papers, selective licence, electrical safety report or 2015 alarm rules) orcompany(let to a company that lets the rooms out: no deposit protection or Renters’ Rights Act papers on that agreement).purposeBuiltBlockFlat: whether a flat is in a purpose-built block of 3 or more flats. Both takenullfor not known.propertyType(house,flatorhmo),bedrooms,occupantsCount,householdsCount.
A home outside England is registered, but Datestone covers England’s rules for now, so it has no obligations. Registration and GET say so in outsideEngland: the country and a note you may print.
A row still waiting on a fact carries question: the fact, the field that answers it, and ask, the question in words. Registration, PATCH and GET also return the property’s openQuestions. Ask your user, then send the answer with PATCH.
detail is a plain sentence about the duty that you may print. legalBasis, when Datestone holds one, names the law behind it. Earlier responses carried that citation in detail, so if you show the law, read it from legalBasis now. status.label follows the date the same way stateLabel does; group by status.word.
{
"key": "4b0c…",
"type": "gas_safety_cert",
"title": "Gas safety certificate (CP12)",
"detail": "Annual gas safety check (CP12) by a Gas Safe engineer.",
"legalBasis": "Gas Safety (Installation and Use) Regulations 1998, regulation 36",
"dueDate": null,
"confidence": "unknown",
"status": { "word": "one_answer", "label": "Not known yet", "tone": "ask" },
"timing": null,
"dueDateReason": {
"code": "needs_answer",
"text": "We can't tell whether this applies until one question is answered: Does it have gas appliances?"
},
"question": {
"fact": "has_gas_appliances",
"field": "hasGasAppliances",
"ask": "Does it have gas appliances?"
}
}Dates
Every date is a plain date (2027-03-14), in both directions; a timestamp is refused. A null dueDate is never “not required”, and must never be aged from any other date. Whether a row is late is timing’s answer, not the date’s: an energy performance certificate (EPC) rated below E is out of date on its rating while it is valid, and an EPC that has run out is no breach until the home is next marketed, sold or let to a tenancy that began after it. Each row says why, where from, and how it stands:
dueDateReason, when there is no date — and on an EPC out of date on its rating:settled,needs_answer,council_to_confirm,fire_service_to_confirm,no_certificate_date,no_epc_date,epc_rating_below_e,no_record_yet,law_sets_no_dateornot_dated, with a sentence you may print. On an EPC that has run out:epc_ran_out_while_let,epc_needed_before_letting,epc_let_after_it_ran_outorepc_was_below_e, with its dated sentence; itstimingisoverdueonly for the third, and none otherwise.epcRating, on the EPC row only: the rating (A to G) of the EPC whose date the row carries, when the register says so.dueDateSource, only when there is a date:certificate_on_file,date_entered,epc_register,check_recorded,tenancy_startorfixed_in_law.timing:overdue,due_soonorin_date— Datestone’s reading of the row, independent of the word. An advisory row keeps the wordgood_practicewhatever its timing.lapsedBeforeAdded: truewhen the date passed before the property was added to Datestone. Say when it ran out, not how long it has been late.
Filing a date
- A certificate or licence takes
expiresDate, and optionallyissuedDate. - A check — smoke and carbon monoxide alarms, legionella, inspections, inventories — takes
issuedDate, the day it was done: not in the future, and not before its tenancy began. An inspection can also carrycadenceMonths, its own cycle. Sent with onlyexpiresDate, a check is recorded as done on the day it first arrives, and the reply says so innote. - The EPC takes
expiresDate; the later of this and the EPC register counts. - A duty whose date comes from somewhere else — deposit protection, right to rent, tenancy papers served — is refused with
422 date_comes_from_elsewhere, never accepted and ignored.
The reply carries the row exactly as the next GET returns it (obligation), and unchanged: true only when every value it wrote was already held. Type keys are Datestone’s own and there are no synonyms: a type this property does not carry comes back 404 type_not_on_property with the property’s validTypes and, where it helps, the reason.
Type keys
| type | duty | applies to |
|---|---|---|
additional_licence | Additional HMO licence (council designation) | the property |
eicr | Electrical safety report (EICR) | the property |
epc_c_2030 | Energy standard equivalent to energy performance certificate (EPC) C by 2030: a plan, not law yet (not in force yet) | the property |
epc_min_e | Energy performance certificate (EPC): valid, rated E or better | the property |
epc_room_lets | EPC when you let rooms | the property |
fire_risk_assessment_shared_areas | Fire risk assessment of the shared areas | the property |
gas_safety_cert | Gas safety certificate (CP12) | the property |
hmo_common_parts | HMO: shared areas and shared appliances kept safe, clean, lit and in repair | the property |
hmo_fire_safety_management | HMO: escape routes clear, fire alarms and equipment working, escape notices | the property |
hmo_licence_alarms | Smoke and carbon monoxide alarms (licence condition) | the property |
hmo_licence_electrical | Electrical installations safe (HMO licence condition) | the property |
hmo_manager_contact_details | HMO: your name, address and phone number to every household, and on display | the property |
hmo_waste_bins | HMO: enough bins for every household | the property |
landlord_ombudsman | Landlord ombudsman membership (not in force yet) | the property |
legionella_assessment | Legionella risk assessment | the property |
mandatory_hmo_licence | Mandatory HMO licence (5+ people, 2+ households) | the property |
periodic_inspection | Periodic inspection | the property |
prs_database_empty_home | Private rented sector (PRS) database: registering an empty home (not in force yet) | the property |
prs_database_registration | Private rented sector (PRS) database: register yourself and each home (not in force yet) | the property |
selective_licence | Selective licence (council designation) | the property |
smoke_co_alarms | Smoke and carbon monoxide alarms | the property |
alarms_working_at_tenancy_start | Alarms proven working at check-in | each tenancy |
deposit_protection | Deposit protection + prescribed information | each tenancy |
hmo_room_clean_at_start | HMO: each room clean when a new occupier moves in, and kept in repair | each tenancy |
inventory_checkin | Check-in inventory | each tenancy |
inventory_checkout | Check-out inventory | each tenancy |
right_to_rent | Right to rent checks | each tenancy |
rra_info_sheet | Renters' Rights Act information sheet (tenancies before 1 May 2026) | each tenancy |
rra_written_statement | Renters' Rights Act written statement of terms (new tenancies) | each tenancy |