Connells Website JSON API (undocumented)

An undocumented, unauthenticated, read-only JSON surface served by the Connells consumer website at https://www.connells.co.uk/api. It is NOT a published Connells Group product — the group publishes no developer portal, no documentation and no specification, and makes no commitment about these routes. They are Next.js App Router route handlers backing the site's branch finder, people directory, testimonials wall and place autocomplete, with content sourced from the Homeflow estate-agency platform that powers the Connells consumer estate. Six operations answered anonymous requests on 2026-07-26 — listBranches, getBranch, listStaff, listTestimonials, listLocations and searchPlaces — while a seventh, /api/properties, returned HTTP 500 on every probe, so no property listings are available. The surface is unversioned, read-only (Allow: GET, HEAD, OPTIONS), rate limited by Cloudflare with HTTP 429 and Retry-After but no forward RateLimit headers, and hard-capped at 12 records per collection because no paging parameter is honoured despite pagination metadata advertising 162 branches and 841 staff. Application errors ride inside HTTP 200 bodies rather than in status codes. Only www.connells.co.uk exposes these handlers; the sibling Connells Group brand sites redirect /api/* away. The OpenAPI in this repo was derived by API Evangelist from live response captures, not published by the provider.

OpenAPI Specification

connells-website-openapi.yml Raw ↑
openapi: 3.1.0
info:
  title: Connells Website JSON API (undocumented)
  version: '2026-07-26'
  summary: Read-only JSON endpoints served by the Connells estate agency website.
  description: >-
    This is NOT a published Connells Group product. Connells Group publishes no
    developer portal, no documentation and no specification of any kind. This
    document was DERIVED by API Evangelist purely from live, anonymous,
    unauthenticated HTTP probes of https://www.connells.co.uk/api on 2026-07-26,
    and every schema below is a faithful transcription of an observed response
    body — nothing was invented, and no field appears here that was not returned
    by the live service.


    The endpoints are Next.js App Router route handlers backing the
    www.connells.co.uk consumer site (branch finder, people directory,
    testimonials wall and location autocomplete). Content is sourced from the
    Homeflow estate-agency platform that powers the Connells consumer estate
    (asset URLs resolve to *.homeflow-assets.co.uk). They are unversioned,
    undocumented, carry no service-level commitment, are rate limited by
    Cloudflare (HTTP 429 with Retry-After) and may change or disappear without
    notice. Treat them as an observation of the estate, not as a contract.


    The sibling Connells Group brand sites (Hamptons, Sequence, Bairstow Eves,
    Fox & Sons, John D Wood, Blundells, Barnard Marcus, William H Brown) run the
    Homeflow Rails application directly and 302-redirect /api/* to their
    homepage; Countrywide 301-redirects; Gascoigne Halman 404s. Only
    www.connells.co.uk exposes these handlers.
  x-provenance: api-evangelist-derived-from-live-probes
  x-published-by-provider: false
  x-probe-date: '2026-07-26'
servers:
  - url: https://www.connells.co.uk/api
    description: Production (undocumented, unversioned, no SLA)
tags:
  - name: Branches
    description: Connells branch (office) directory.
  - name: People
    description: Connells staff directory.
  - name: Testimonials
    description: Published customer testimonials.
  - name: Locations
    description: Geographic locations and place-name lookup.
paths:
  /branches:
    get:
      operationId: listBranches
      summary: List Connells branches
      description: >-
        Returns the first page of the Connells branch directory. Observed
        2026-07-26 as 162 branches across 14 pages of 12. No query parameter was
        honoured — page, pageSize, per_page, limit, lat, lng, search, q, query,
        slug, service, distance and isLettingsEnabled were each probed and all
        returned page 1 unchanged. OPTIONS reports "Allow: GET, HEAD, OPTIONS".
      tags: [Branches]
      security: []
      responses:
        '200':
          description: A page of branches.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BranchListEnvelope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /branches/{branchId}:
    get:
      operationId: getBranch
      summary: Get one branch by numeric id
      description: >-
        Returns a single branch by its numeric id (observed: 8020 = Abingdon).
        The path segment must be the numeric id; passing the branch slug
        ("abingdon") returns HTTP 200 with a null result rather than a 404.
      tags: [Branches]
      security: []
      parameters:
        - name: branchId
          in: path
          required: true
          description: Numeric branch identifier, as returned by listBranches.
          schema:
            type: integer
          example: 8020
      responses:
        '200':
          description: The branch, or a null result when no branch matches.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BranchEnvelope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /staff:
    get:
      operationId: listStaff
      summary: List Connells staff
      description: >-
        Returns the first page of the Connells people directory. Observed
        2026-07-26 as 841 staff across 71 pages of 12. Same envelope and same
        non-honoured pagination behaviour as listBranches.
      tags: [People]
      security: []
      responses:
        '200':
          description: A page of staff members.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StaffListEnvelope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /testimonials:
    get:
      operationId: listTestimonials
      summary: List published customer testimonials
      description: >-
        Returns 12 published customer testimonials. Note the envelope differs
        from the others: `pagination` is null and the error key is singular
        (`error`, not `errors`).
      tags: [Testimonials]
      security: []
      responses:
        '200':
          description: Testimonials.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TestimonialListEnvelope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /locations:
    get:
      operationId: listLocations
      summary: List locations
      description: >-
        Returns a bare JSON array (no envelope) of 12 location records. Ids are
        24-character hexadecimal strings (MongoDB ObjectId form), consistent
        with the Homeflow platform behind the site. Several records were
        observed with empty `name` and `urlLabel` values.
      tags: [Locations]
      security: []
      responses:
        '200':
          description: An array of locations.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Location'
        '429':
          $ref: '#/components/responses/RateLimited'
  /places:
    get:
      operationId: searchPlaces
      summary: Look up places by name
      description: >-
        Place-name autocomplete. Requires the `name` query parameter; without it
        the service returns HTTP 200 with an empty result set and the error
        "Name required for places search". Results are returned as positional
        arrays of three strings — [display label, place id, place slug] — rather
        than as objects.
      tags: [Locations]
      security: []
      parameters:
        - name: name
          in: query
          required: true
          description: Free-text place name to search for.
          schema:
            type: string
          example: bedford
      responses:
        '200':
          description: Matching places, or an error entry when `name` is absent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaceSearchEnvelope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /properties:
    get:
      operationId: listProperties
      summary: Property listings (route present, non-functional)
      description: >-
        This route handler exists but returned HTTP 500 with an empty body on
        every probe on 2026-07-26 — bare, and with location, search, type and
        page query parameters. It is documented here only to record that the
        route is present and failing; no successful response has ever been
        observed, so no 200 schema is asserted. Property listings on
        www.connells.co.uk are server-rendered from the Homeflow platform, not
        fetched from this endpoint by the browser.
      tags: [Branches]
      security: []
      responses:
        '500':
          description: Observed on every probe. Empty body.
        '429':
          $ref: '#/components/responses/RateLimited'
components:
  responses:
    RateLimited:
      description: >-
        Rate limited at the Cloudflare edge. Body is the plain-text string "Too
        many requests" and a Retry-After header is supplied (34 seconds
        observed).
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
          example: 34
      content:
        text/plain:
          schema:
            type: string
          example: Too many requests
  schemas:
    Pagination:
      type: object
      description: Pagination envelope returned by the list endpoints.
      properties:
        totalCount:
          type: integer
          example: 162
        currentPage:
          type: integer
          example: 1
        pageSize:
          type: integer
          example: 12
        pageCount:
          type: integer
          example: 14
        hasPrevPage:
          type: boolean
        hasNextPage:
          type: boolean
    ApiError:
      type: object
      properties:
        message:
          type: string
          example: Name required for places search
    BranchListEnvelope:
      type: object
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/Branch'
        pagination:
          $ref: '#/components/schemas/Pagination'
        errors:
          type: [array, 'null']
          items:
            $ref: '#/components/schemas/ApiError'
    BranchEnvelope:
      type: object
      properties:
        result:
          oneOf:
            - $ref: '#/components/schemas/Branch'
            - type: 'null'
        errors:
          type: [array, 'null']
          items:
            $ref: '#/components/schemas/ApiError'
    StaffListEnvelope:
      type: object
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/StaffMember'
        pagination:
          $ref: '#/components/schemas/Pagination'
        errors:
          type: [array, 'null']
          items:
            $ref: '#/components/schemas/ApiError'
    TestimonialListEnvelope:
      type: object
      description: >-
        Note the singular `error` key and the null `pagination` — this envelope
        is not consistent with the branch and staff envelopes.
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/Testimonial'
        pagination:
          type: 'null'
        error:
          type: [object, 'null']
    PlaceSearchEnvelope:
      type: object
      properties:
        results:
          type: array
          description: Each match is a positional 3-string array.
          items:
            $ref: '#/components/schemas/PlaceMatch'
        errors:
          type: [array, 'null']
          items:
            $ref: '#/components/schemas/ApiError'
    PlaceMatch:
      type: array
      description: '[display label, place id, place slug]'
      minItems: 3
      maxItems: 3
      items:
        type: string
      example:
        - Bedford, Bedfordshire
        - 51e7c40773dadaf60feea517
        - bedford
    Branch:
      type: object
      description: A Connells branch (high-street office).
      properties:
        id:
          type: integer
          example: 8020
        slug:
          type: string
          example: abingdon
        name:
          type: string
          example: Abingdon
        mainPhoto:
          type: [string, 'null']
          format: uri
          description: Branch photograph, served from the Homeflow asset CDN.
        email:
          type: [string, 'null']
          format: email
        description:
          type: [string, 'null']
          description: Long-form marketing copy for the branch.
        lat:
          type: [number, 'null']
          example: 51.6701702207
        lng:
          type: [number, 'null']
          example: -1.2827574378
        displayAddress:
          type: [string, 'null']
          description: Newline-delimited postal address.
        contactTelephone:
          type: [string, 'null']
        urlLabel:
          type: [string, 'null']
        salesTelephone:
          type: [string, 'null']
        lettingsTelephone:
          type: [string, 'null']
        branchManager:
          oneOf:
            - $ref: '#/components/schemas/StaffMember'
            - type: 'null'
        services:
          type: array
          items:
            $ref: '#/components/schemas/BranchService'
        agencyId:
          type: [integer, 'null']
          example: 909
        isSalesEnabled:
          type: [boolean, 'null']
        isLettingsEnabled:
          type: [boolean, 'null']
        departments:
          type: array
          description: Observed empty on every record sampled.
          items:
            type: object
        distance:
          type: [number, 'null']
          description: Observed null; presumably populated by a proximity search.
        branchUrl:
          type: [string, 'null']
          example: /estate-agents/abingdon
    BranchService:
      type: object
      description: A service line offered at a branch.
      properties:
        service:
          type: string
          example: Lettings
        phone_number:
          type: [string, 'null']
        fax_number:
          type: [string, 'null']
        email_address:
          type: [string, 'null']
        enabled:
          type: [boolean, 'null']
    StaffMember:
      type: object
      description: >-
        A Connells colleague. The same shape is embedded as a branch's
        `branchManager`.
      properties:
        id:
          type: integer
          example: 23232373
        slug:
          type: string
          example: paul-barron
        name:
          type: string
          example: Paul Barron
        jobTitle:
          type: [string, 'null']
          example: Instructions Manager
        avatar:
          type: [string, 'null']
          format: uri
        mobileNumber:
          type: [string, 'null']
          description: Observed null on every record sampled.
        email:
          type: [string, 'null']
          description: Observed null on every record sampled.
        urlLabel:
          type: [string, 'null']
        team:
          type: [string, 'null']
        shortBio:
          type: [string, 'null']
        longBio:
          type: [string, 'null']
        branchId:
          type: [integer, 'null']
          description: Foreign key to Branch.id.
          example: 8020
    Testimonial:
      type: object
      properties:
        id:
          type: integer
          example: 417891
        name:
          type: string
          description: Partially redacted customer name as published on the site.
        content:
          type: string
        displayDate:
          type: string
          description: Human-formatted date, DD/MM/YYYY.
          example: 24/07/2026
        contactCounty:
          type: [string, 'null']
          example: Cambridgeshire
        createdAt:
          type: string
          format: date-time
          example: '2026-07-24T13:29:00.000Z'
    Location:
      type: object
      properties:
        id:
          type: string
          description: 24-character hexadecimal identifier (MongoDB ObjectId form).
          example: 590a049361034ea359e75f51
        name:
          type: string
          description: Observed empty on several records.
        countyName:
          type: string
          example: West Yorkshire
        urlLabel:
          type: string
          description: Observed empty on several records.