{"openapi":"3.1.0","info":{"title":"Sweetstep API","version":"1.0.0","description":"UK transport connectivity lookup API. Scores derived from the DfT Connectivity Metric 2025 (published — not formally designated as National Statistics, OGL v3.0). Every response includes a provenance block.","contact":{"name":"Sweetstep","url":"https://sweetstep.co.uk/contact"},"license":{"name":"Open Government Licence v3.0","url":"https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/"}},"servers":[{"url":"https://api.sweetstep.co.uk/v1","description":"Production"},{"url":"http://localhost:3000/api/v1","description":"Local development"}],"paths":{"/postcode/{postcode}":{"get":{"operationId":"getPostcode","summary":"Resolves a UK postcode to its Output Area (or Scottish Data Zone) and returns its scores.","description":"Returns full OA-level connectivity scores for a UK postcode. Postcode is mapped to an Output Area (OA) via ONS ONSPD lookup. Includes overall scores, per-destination scores, risk flags, national rank, and a mandatory provenance block.","parameters":[{"name":"postcode","in":"path","required":true,"description":"UK postcode (e.g. SW1A 1AA or SW1A1AA)","schema":{"type":"string","example":"SW1A 1AA"}}],"responses":{"200":{"description":"Successful response with OA-level scores","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OaResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimit"}},"x-rate-limit":"60 requests/hour per IP (public)"}},"/oa/{oa_code}":{"get":{"operationId":"getOa","summary":"Scores for an Output Area code directly, skipping postcode lookup.","description":"Returns full connectivity scores for an Output Area (OA21 code, e.g. E00012345). Useful for programmatic queries when the OA code is already known.","parameters":[{"name":"oa_code","in":"path","required":true,"description":"Output Area code (OA21 format, e.g. E00012345)","schema":{"type":"string","example":"E00012345"}}],"responses":{"200":{"description":"Successful response with OA-level scores","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OaResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimit"}},"x-rate-limit":"60 requests/hour per IP (public)"}},"/area/lad/{lad_code}":{"get":{"operationId":"getLad","summary":"Average scores across every Output Area in a local authority.","description":"Returns mean scores across all Output Areas in a Local Authority District. Suitable for LAD-level benchmarking, transport strategy evidence, and comparing individual OA scores against local authority averages.","parameters":[{"name":"lad_code","in":"path","required":true,"description":"LAD code (e.g. E07000008)","schema":{"type":"string","example":"E07000008"}}],"responses":{"200":{"description":"Successful response with LAD mean scores","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AreaMeanResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimit"}},"x-rate-limit":"60 requests/hour per IP (public)"}},"/area/region/{rgn_code}":{"get":{"operationId":"getRegion","summary":"Average scores across every Output Area in a region.","description":"Returns mean scores across all Output Areas in a region. Suitable for regional context in transport planning and comparison.","parameters":[{"name":"rgn_code","in":"path","required":true,"description":"Region code (e.g. E12000007)","schema":{"type":"string","example":"E12000007"}}],"responses":{"200":{"description":"Successful response with region mean scores","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AreaMeanResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimit"}},"x-rate-limit":"60 requests/hour per IP (public)"}},"/batch":{"post":{"operationId":"batchLookup","summary":"Up to 100 postcodes in one request. Each postcode counts as one request towards your limit. Some may fail while others succeed: check each result’s status.","description":"Look up connectivity scores for multiple postcodes in a single request. Requires API key authentication (Bearer token). Maximum 100 postcodes per request. B2B Standard tier: 1,000 req/hr.","security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["postcodes"],"properties":{"postcodes":{"type":"array","items":{"type":"string"},"maxItems":100,"description":"Array of UK postcodes (max 100)","example":["SW1A 1AA","EC1A 1BB"]}}}}}},"responses":{"200":{"description":"Batch results — one entry per postcode","content":{"application/json":{"schema":{"type":"object","required":["results","summary","provenance"],"properties":{"results":{"type":"array","description":"One entry per postcode, in request order. HTTP 200 whenever the body is valid: check each status.","items":{"type":"object","required":["postcode","status"],"properties":{"postcode":{"type":"string"},"status":{"type":"string","enum":["ok","error"]},"data":{"$ref":"#/components/schemas/OaResponse"},"error":{"type":"string"},"message":{"type":"string"}}}},"summary":{"type":"object","properties":{"total":{"type":"integer"},"succeeded":{"type":"integer"},"failed":{"type":"integer"}}},"provenance":{"$ref":"#/components/schemas/ProvenanceBlock"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorised"},"429":{"$ref":"#/components/responses/RateLimit"}},"x-rate-limit":"1,000 requests/hour per API key (B2B Standard)"}},"/corrections":{"post":{"operationId":"submitCorrection","summary":"Report a score that looks wrong for an Output Area. Corrections are reviewed before any data changes. Requests with an API key are tagged as a higher reporter tier.","description":"Files a correction report against one Output Area score. Reports are moderated; published scores change only after review. An API key is optional.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["oa_code","mode","destination","description"],"properties":{"oa_code":{"type":"string","description":"OA21CD code","example":"E00046370"},"mode":{"type":"string","enum":["walking","cycling","publicTransport","driving","overall"]},"destination":{"type":"string","enum":["employment","education","healthcare","shopping","leisureAndCommunity","residential","overall"]},"description":{"type":"string","maxLength":1000}}}}}},"responses":{"201":{"description":"Correction received","content":{"application/json":{"schema":{"type":"object","required":["id","status","message","provenance"],"properties":{"id":{"type":"string"},"status":{"type":"string","enum":["new"]},"message":{"type":"string"},"provenance":{"$ref":"#/components/schemas/ProvenanceBlock"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorised"},"429":{"$ref":"#/components/responses/RateLimit"}},"x-rate-limit":"60 requests/hour per IP (public); per-account limit when signed in"}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key issued by Sweetstep. Keys are stored securely as one-way hashes, so we can never see or recover your key. Displayed once at creation."}},"schemas":{"ProvenanceBlock":{"type":"object","required":["dataset","dataset_version","publication_status","methodology","geographic_unit","approximate_population","score_type","score_interpretation","limitations","licence","retrieved_at"],"properties":{"dataset":{"type":"string","example":"DfT Connectivity Metric 2025"},"dataset_version":{"type":"string","example":"2025"},"publication_status":{"type":"string","example":"published — not formally designated as National Statistics"},"methodology":{"type":"string","example":"normalised_0_100_index"},"geographic_unit":{"type":"string","example":"Output Area (OA21)"},"approximate_population":{"type":"integer","example":300},"score_type":{"type":"string","example":"modelled_accessibility"},"score_interpretation":{"type":"string"},"limitations":{"type":"array","items":{"type":"string"}},"licence":{"type":"string","example":"Open Government Licence v3.0"},"retrieved_at":{"type":"string","format":"date-time"},"pipeline_id":{"type":"string"},"ingested_at":{"type":"string","format":"date-time"}}},"ModeScores":{"type":"object","required":["walking","cycling","public_transport","driving"],"properties":{"walking":{"type":"number","minimum":0,"maximum":100},"cycling":{"type":"number","minimum":0,"maximum":100},"public_transport":{"type":"number","minimum":0,"maximum":100},"driving":{"type":"number","minimum":0,"maximum":100}}},"Flags":{"type":"object","required":["car_dependent","zero_healthcare_walking","walking_gt_driving","active_travel_opportunity"],"properties":{"car_dependent":{"type":"boolean","description":"Driving Overall > 80 AND walking Overall < 40 AND PT Overall < 50"},"zero_healthcare_walking":{"type":"boolean","description":"Healthcare walking score < 20 (near-zero walking access to GPs)"},"walking_gt_driving":{"type":"boolean","description":"Walking overall > driving overall"},"active_travel_opportunity":{"type":"boolean","description":"Cycling overall >= 25 points below driving AND cycling overall < 50"}}},"OaResponse":{"type":"object","required":["oa_code","lad","region","country","scores","national_rank","flags","provenance"],"properties":{"oa_code":{"type":"string","example":"E00012345"},"postcode":{"type":"string","nullable":true,"example":"SW1A 1AA"},"lad":{"type":"object","required":["code","name"],"properties":{"code":{"type":"string"},"name":{"type":"string"}}},"region":{"type":"object","required":["code","name"],"properties":{"code":{"type":"string"},"name":{"type":"string"}}},"country":{"type":"string","example":"England"},"scores":{"type":"object","required":["overall","by_destination"],"properties":{"overall":{"allOf":[{"$ref":"#/components/schemas/ModeScores"},{"type":"object","required":["overall"],"properties":{"overall":{"type":"number","minimum":0,"maximum":100}}}]},"by_destination":{"type":"object","required":["employment","education","healthcare","shopping","leisure_community","residential"],"additionalProperties":{"$ref":"#/components/schemas/ModeScores"}},"destination_overall":{"type":"object","description":"DfT's all-mode score for each destination type (its '(overall)' columns, e.g. Healthcare (overall)).","additionalProperties":{"type":"number","nullable":true,"minimum":0,"maximum":100}}}},"national_rank":{"type":"object","properties":{"overall":{"type":"integer","nullable":true},"walking":{"type":"integer","nullable":true},"cycling":{"type":"integer","nullable":true},"public_transport":{"type":"integer","nullable":true},"driving":{"type":"integer","nullable":true},"destination_overall":{"type":"object","description":"National rank (1 = best) of each destination type's all-mode score.","additionalProperties":{"type":"integer","nullable":true}}}},"flags":{"$ref":"#/components/schemas/Flags"},"provenance":{"$ref":"#/components/schemas/ProvenanceBlock"}}},"AreaMeanResponse":{"type":"object","required":["oa_count","mean_scores","provenance"],"properties":{"oa_count":{"type":"integer","description":"Number of Output Areas in the area"},"mean_scores":{"type":"object","required":["overall","walking","cycling","public_transport","driving"],"properties":{"overall":{"type":"number"},"walking":{"type":"number"},"cycling":{"type":"number"},"public_transport":{"type":"number"},"driving":{"type":"number"}}},"provenance":{"$ref":"#/components/schemas/ProvenanceBlock"}}},"ApiErrorResponse":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["INVALID_POSTCODE","POSTCODE_NOT_FOUND","OA_NOT_FOUND","LAD_NOT_FOUND","INVALID_RGN_CODE","REGION_NOT_FOUND","RATE_LIMIT_EXCEEDED","INVALID_API_KEY","INVALID_REQUEST","INTERNAL_ERROR","SERVICE_UNAVAILABLE","FORBIDDEN"]},"message":{"type":"string"}},"x-sweetstep-errors":[{"code":"INVALID_POSTCODE","status":"400","meaning":"Not a valid postcode format."},{"code":"POSTCODE_NOT_FOUND","status":"404","meaning":"Valid format, but not covered (for example, Northern Ireland)."},{"code":"OA_NOT_FOUND","status":"404","meaning":"Output Area code not in the dataset."},{"code":"LAD_NOT_FOUND","status":"404","meaning":"Local authority code not found."},{"code":"INVALID_RGN_CODE","status":"400","meaning":"Not a valid region code format."},{"code":"REGION_NOT_FOUND","status":"404","meaning":"Region code not found."},{"code":"INVALID_API_KEY","status":"401","meaning":"Missing, malformed or revoked key."},{"code":"RATE_LIMIT_EXCEEDED","status":"429","meaning":"Too many requests. See `Retry-After`."},{"code":"INVALID_REQUEST","status":"400","meaning":"Request body failed validation (batch and corrections)."},{"code":"INTERNAL_ERROR","status":"500 / 503","meaning":"Service unavailable. Retry with backoff."}]}},"responses":{"BadRequest":{"description":"Bad request — invalid postcode format or missing required parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"NotFound":{"description":"Postcode or OA code not found in dataset","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"RateLimit":{"description":"Rate limit exceeded. Retry after the indicated period.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"Unauthorised":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}}