openapi: 3.1.0
info:
  title: Ringfence API
  version: 1.0.0
  description: >
    Every postal code and town within a radius of a location, worldwide.
    Authenticate with an organization API key: `Authorization: Bearer rf_live_...`
    (or `X-API-Key: rf_live_...`). Keys are created by signed-in users on the Account page.
    AI agents that support MCP can instead connect to `POST /mcp` with the same key.
servers:
  - url: /
security:
  - apiKey: []
paths:
  /api/v1/search:
    get:
      operationId: radiusSearch
      summary: Postal codes and towns within a radius
      parameters:
        - { name: location, in: query, schema: { type: string }, description: 'Postal code, "Town, State", town, or street address. Required unless lat and lng are sent.' }
        - { name: lat, in: query, schema: { type: number } }
        - { name: lng, in: query, schema: { type: number } }
        - { name: radius, in: query, required: true, schema: { type: number, minimum: 0 }, description: Inclusive radius. }
        - { name: unit, in: query, schema: { type: string, enum: [mi, km], default: mi } }
        - { name: country, in: query, schema: { type: string }, description: ISO 3166-1 alpha-2 code that narrows location matching. }
        - { name: auto_pick, in: query, schema: { type: boolean, default: false }, description: Use the best match instead of returning candidates. }
        - { name: format, in: query, schema: { type: string, enum: [json, csv, zip_list], default: json } }
      responses:
        "200":
          description: Results, or candidates when the location is ambiguous.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/SearchOk"
                  - $ref: "#/components/schemas/Ambiguous"
                  - $ref: "#/components/schemas/NotFound"
            text/csv:
              schema: { type: string }
            text/plain:
              schema: { type: string, description: One postal code per line. }
        "400": { $ref: "#/components/responses/Error" }
        "401": { $ref: "#/components/responses/Error" }
        "402": { $ref: "#/components/responses/Error" }
    post:
      operationId: radiusSearchPost
      summary: Same as GET, with a JSON body
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [radius]
              properties:
                location: { type: string }
                lat: { type: number }
                lng: { type: number }
                radius: { type: number }
                unit: { type: string, enum: [mi, km] }
                country: { type: string }
                auto_pick: { type: boolean }
                format: { type: string, enum: [json, csv, zip_list] }
      responses:
        "200": { description: See GET. }
  /api/v1/countries:
    get:
      operationId: listCountries
      security: []
      summary: Countries, and whether postal codes are available for each
      responses:
        "200": { description: OK }
components:
  securitySchemes:
    apiKey: { type: http, scheme: bearer }
  responses:
    Error:
      description: Error
      content:
        application/json:
          schema:
            type: object
            properties: { error: { type: string }, message: { type: string } }
  schemas:
    Row:
      type: object
      properties:
        postal_code: { type: string, description: Empty for towns in areas without postal data. }
        distance: { type: number }
        town: { type: string }
        state: { type: string }
        county: { type: string }
        country: { type: string }
        country_code: { type: string }
        lat: { type: number }
        lng: { type: number }
    SearchOk:
      type: object
      properties:
        status: { const: ok }
        center: { type: object, properties: { label: { type: string }, lat: { type: number }, lng: { type: number } } }
        radius: { type: number }
        unit: { type: string }
        counts: { type: object, properties: { postal_codes: { type: integer }, rows: { type: integer } } }
        truncated: { type: boolean }
        postal_codes: { type: array, items: { type: string } }
        rows: { type: array, items: { $ref: "#/components/schemas/Row" } }
    Ambiguous:
      type: object
      properties:
        status: { const: ambiguous }
        message: { type: string }
        candidates:
          type: array
          items:
            type: object
            properties: { label: { type: string }, lat: { type: number }, lng: { type: number }, country_code: { type: string }, kind: { type: string } }
    NotFound:
      type: object
      properties: { status: { const: not_found }, message: { type: string } }
