Webby Directory API
Search local businesses and read their profiles, prices, the areas they cover, checked accreditations, availability and reviews. Public data needs no key. Download the description as openapi.yaml to load it into any OpenAPI tool.
openapi: 3.0.3
info:
title: Webby Directory API
version: "1.0"
description: |
Read-only access to Webby Directory: local businesses with their prices,
the areas they cover, checked accreditations and insurance, recent work
and reviews collected by My Webby.
No key is needed for public data (60 requests a minute per IP). A partner
key raises the limit. A business's own site key reads only that business,
including its full postcode, and cannot search.
Send a key as `X-Directory-Key: <key>` or `Authorization: Bearer <key>`.
Responses carry an `ETag`; send it back as `If-None-Match` to get a `304`.
Every field a business can change comes with when it was last changed and
by whom (`field_updates`). Reviews are never Google's.
servers:
- url: https://webbydirectory.co.uk/api/directory/v1
security:
- {}
- directoryKey: []
- bearer: []
paths:
/search:
get:
summary: Find businesses
description: Ordered by match, checked trust, reviews, profile freshness and availability. Nobody pays to be placed.
parameters:
- { name: q, in: query, schema: { type: string, maxLength: 100 }, description: "Words, a trade or a kind of job" }
- { name: town, in: query, schema: { type: string }, description: "Town slug, e.g. norwich" }
- { name: category, in: query, schema: { type: string }, description: "Trade slug, e.g. plumbing" }
- { name: service, in: query, schema: { type: string }, description: "Kind of job slug, e.g. boiler-repair" }
- { name: postcode, in: query, schema: { type: string }, description: "Full postcode or district; only businesses covering it" }
- { name: accepting_work, in: query, schema: { type: boolean } }
- { name: page, in: query, schema: { type: integer, minimum: 1 } }
- { name: per_page, in: query, schema: { type: integer, minimum: 1, maximum: 50, default: 20 } }
responses:
"200":
description: A page of results
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: "#/components/schemas/BusinessCard" } }
meta: { $ref: "#/components/schemas/SearchMeta" }
"403": { description: A site key cannot search }
"422": { description: A filter is not valid }
"429": { description: Too many requests }
/self:
get:
summary: A site key's own business
description: The same profile as /businesses/{slug}, with the full postcode. Site keys only.
responses:
"200":
description: The profile
content:
application/json:
schema: { type: object, properties: { data: { $ref: "#/components/schemas/Business" } } }
"403": { description: Not a site key }
/businesses/{slug}:
get:
summary: One business's profile
parameters:
- { $ref: "#/components/parameters/Slug" }
- { name: include, in: query, schema: { type: string, enum: [jsonld] }, description: "jsonld adds the schema.org block the profile page embeds" }
responses:
"200":
description: The profile
content:
application/json:
schema: { type: object, properties: { data: { $ref: "#/components/schemas/Business" } } }
"404": { description: Not listed, or not this site key's business }
/businesses/{slug}/enquiries:
post:
summary: Send an enquiry to a business
description: |
With a partner key allowed to send enquiries, it goes to the business at once (201).
Without one, we email the sender a link and send it only once they confirm, within 24 hours (202).
Limits: 5 an hour from one email or phone, 50 a day to one business.
parameters: [{ $ref: "#/components/parameters/Slug" }]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name, message, consent]
properties:
name: { type: string, maxLength: 120 }
email: { type: string, format: email, description: "Email or phone is needed; email to hold and confirm" }
phone: { type: string, maxLength: 30 }
message: { type: string, maxLength: 3000 }
service: { type: string, maxLength: 200 }
postcode: { type: string, maxLength: 10 }
preferred_dates: { type: string, maxLength: 300 }
consent: { type: boolean, description: "The sender agrees the business may contact them about this" }
responses:
"201": { description: Sent to the business }
"202": { description: Held until the sender confirms by email }
"404": { description: Not listed }
"422": { description: Missing or not valid, or the business does not take enquiries }
"429": { description: Too many enquiries }
/businesses/{slug}/services:
get:
summary: A business's services and prices
parameters: [{ $ref: "#/components/parameters/Slug" }]
responses:
"200":
description: Services
content:
application/json:
schema: { type: object, properties: { data: { type: array, items: { $ref: "#/components/schemas/Service" } } } }
"404": { description: Not found }
/businesses/{slug}/reviews:
get:
summary: Reviews collected by My Webby, newest first
parameters:
- { $ref: "#/components/parameters/Slug" }
- { name: page, in: query, schema: { type: integer, minimum: 1 } }
responses:
"200":
description: A page of reviews
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: "#/components/schemas/Review" } }
meta: { type: object }
"404": { description: Not found }
/businesses/{slug}/availability:
get:
summary: Whether a business is taking work, and how soon
parameters: [{ $ref: "#/components/parameters/Slug" }]
responses:
"200":
description: Availability
content:
application/json:
schema: { type: object, properties: { data: { $ref: "#/components/schemas/Availability" } } }
"404": { description: Not found }
/businesses/{slug}/coverage:
get:
summary: The postcode districts a business covers, and whether it covers one postcode
parameters:
- { $ref: "#/components/parameters/Slug" }
- { name: postcode, in: query, schema: { type: string } }
responses:
"200":
description: Coverage
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
postcode: { type: string, nullable: true }
district: { type: string, nullable: true }
covered: { type: boolean, nullable: true }
districts: { type: array, items: { type: string } }
"404": { description: Not found }
/categories:
get:
summary: Trades and the kinds of job under each
responses:
"200": { description: The taxonomy }
/towns:
get:
summary: Towns the directory is live in
responses:
"200": { description: Towns }
components:
securitySchemes:
directoryKey: { type: apiKey, in: header, name: X-Directory-Key }
bearer: { type: http, scheme: bearer }
parameters:
Slug: { name: slug, in: path, required: true, schema: { type: string } }
schemas:
Ref:
type: object
nullable: true
properties: { slug: { type: string }, name: { type: string } }
Price:
type: object
properties:
type: { type: string, enum: [fixed, from, range, quote_only] }
min: { type: number, nullable: true, description: Pounds }
max: { type: number, nullable: true, description: Pounds }
unit: { type: string, nullable: true, enum: [job, hour, m2, day] }
callout_fee: { type: number, nullable: true, description: Pounds }
FieldUpdates:
type: object
description: Field name to when it last changed and who changed it
additionalProperties:
type: object
properties:
updated_at: { type: string, format: date-time, nullable: true }
by: { type: string, enum: [business, directory, import] }
Availability:
type: object
properties:
accepting_work: { type: boolean }
next_available: { type: string, format: date, nullable: true }
lead_time_days: { type: integer, nullable: true }
emergency_today: { type: boolean }
BusinessCard:
type: object
properties:
slug: { type: string }
url: { type: string }
name: { type: string }
summary: { type: string, nullable: true }
logo_url: { type: string, nullable: true }
listing_type: { type: string, enum: [basic, enhanced] }
category: { $ref: "#/components/schemas/Ref" }
town: { $ref: "#/components/schemas/Ref" }
location: { $ref: "#/components/schemas/Location" }
phone: { type: string, nullable: true }
website: { type: string, nullable: true }
rating: { type: object, nullable: true, properties: { average: { type: number }, count: { type: integer } } }
verified: { type: object, properties: { accreditations: { type: array, items: { type: string } }, insured: { type: boolean } } }
main_services: { type: array, items: { type: object, properties: { name: { type: string }, price: { $ref: "#/components/schemas/Price" } } } }
accepting_work: { type: boolean }
next_available: { type: string, format: date, nullable: true }
updated_at: { type: string, format: date-time, nullable: true }
SearchMeta:
type: object
properties:
total: { type: integer }
page: { type: integer }
per_page: { type: integer }
last_page: { type: integer }
filters: { type: object }
ranking: { type: string }
Location:
type: object
properties:
town: { type: string, nullable: true }
district: { type: string, nullable: true }
postcode: { type: string, nullable: true, description: Only for the business's own site key, or when the business shows its address }
Service:
type: object
properties:
name: { type: string }
kind: { $ref: "#/components/schemas/Ref" }
description: { type: string, nullable: true }
price: { $ref: "#/components/schemas/Price" }
typical_duration: { type: string, nullable: true }
emergency_available: { type: boolean }
is_main_service: { type: boolean }
field_updates: { $ref: "#/components/schemas/FieldUpdates" }
Review:
type: object
properties:
rating: { type: integer, minimum: 1, maximum: 5 }
body: { type: string, nullable: true }
author: { type: string, description: First name and initial }
date: { type: string, format: date }
verified_customer: { type: boolean }
service: { type: string, nullable: true }
reply: { type: object, nullable: true, properties: { body: { type: string }, date: { type: string, format: date } } }
Business:
type: object
properties:
slug: { type: string }
url: { type: string }
name: { type: string }
legal_name: { type: string, nullable: true }
companies_house_no: { type: string, nullable: true }
listing_type: { type: string, enum: [basic, enhanced] }
logo_url: { type: string, nullable: true }
summary: { type: string, nullable: true }
description: { type: string, nullable: true }
established_year: { type: integer, nullable: true }
category: { $ref: "#/components/schemas/Ref" }
town: { $ref: "#/components/schemas/Ref" }
location: { $ref: "#/components/schemas/Location" }
contact:
type: object
properties:
phone: { type: string, nullable: true }
whatsapp: { type: string, nullable: true }
email: { type: string, nullable: true }
website: { type: string, nullable: true }
opening_hours: { type: object, nullable: true, description: "Day name to {open, close, closed}" }
payment_methods: { type: array, items: { type: string } }
languages: { type: array, items: { type: string } }
accepting_work: { type: boolean }
lead_time_days: { type: integer, nullable: true }
google: { type: object, nullable: true, description: A link to the business on Google Maps only; no Google data is copied }
services: { type: array, items: { $ref: "#/components/schemas/Service" } }
service_area: { type: object, nullable: true }
coverage: { type: array, items: { type: string }, description: Postcode districts covered }
trust:
type: object
description: Only accreditations and insurance our staff have checked, and that have not expired
properties:
accreditations: { type: array, items: { type: object } }
insurances: { type: array, items: { type: object } }
work: { type: array, items: { type: object } }
photos: { type: array, items: { type: object, properties: { url: { type: string }, alt: { type: string, nullable: true }, role: { type: string } } } }
reviews:
type: object
properties:
average: { type: number, nullable: true }
count: { type: integer }
themes: { type: array, items: { type: string } }
source: { type: string }
latest: { type: array, items: { $ref: "#/components/schemas/Review" } }
availability: { $ref: "#/components/schemas/Availability" }
updated_at: { type: string, format: date-time, nullable: true }
field_updates: { $ref: "#/components/schemas/FieldUpdates" }
claim_url: { type: string, nullable: true }
enquiry_url: { type: string, nullable: true, description: The enquiry form, when the business takes enquiries }
review_url: { type: string, nullable: true, description: Where a customer can leave a review }
removal_url: { type: string }
jsonld: { type: object, description: "Only with include=jsonld" }