Skip to the page
Webby Directory

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" }