{
  "openapi": "3.1.0",
  "info": {
    "title": "GBG Reach REST API",
    "version": "1.0.0",
    "summary": "Real-time address, email, and phone verification with agent-actionable decisioning.",
    "description": "Reach verifies contact data against Loqate's global reference data and returns a confidence score (0–1), a policy-driven **accept / review / reject** recommendation, and standardized output.\n\nThis REST API is one surface over the same decisioning engine as the Reach CLI and MCP server — identical input produces identical output across all three.\n\n## Getting access\n\nReach requires a Loqate API key. **New to Loqate?** Explore plans, start a free trial, or talk to sales at [loqate.com](https://www.loqate.com/en-gb/pricing/). Existing customers can generate and manage keys at [account.loqate.com](https://account.loqate.com).\n\n**Authentication.** Every endpoint requires a Loqate API key presented as a bearer token: `Authorization: Bearer <LOQATE_API_KEY>`. The key can alternatively be passed in the request body as `key`.\n\n**Decisions are not errors.** A `reject` recommendation is a successful `200` response with `recommendation: \"reject\"` in the body. Non-2xx status codes indicate transport/validation problems, never a business decision.",
    "termsOfService": "https://www.loqate.com/en-gb/legal/terms-and-conditions/",
    "contact": { "name": "Loqate Sales & Support", "url": "https://www.loqate.com/en-gb/contact-us/", "email": "support@loqate.com" },
    "license": { "name": "Proprietary — GB Group plc. Use governed by the customer's existing Loqate agreement.", "url": "https://github.com/gbgplc/lqt/blob/main/LICENSE" }
  },
  "servers": [
    { "url": "https://reach.prod.fabric.gbgplatforms.com", "description": "Production" }
  ],
  "security": [ { "bearerAuth": [] } ],
  "externalDocs": { "description": "Loqate — product, pricing, and how to buy", "url": "https://www.loqate.com" },
  "tags": [
    { "name": "verify", "description": "Verification endpoints" },
    { "name": "policies", "description": "Decisioning policies" }
  ],
  "paths": {
    "/v1/verify/address": {
      "post": {
        "tags": ["verify"],
        "summary": "Verify an address",
        "operationId": "verifyAddress",
        "description": "Verify a free-form or structured address. Returns a confidence score, match level, AVC code, and a policy-driven recommendation. Set `detect_country: true` to guess a missing country from the address. Set `suggest: true` to get alternative addresses suggested by Loqate whenever the result is not accepted, or is accepted below the policy's confidence floor (`suggest_below_confidence`, overridable per call with `suggest_below`).\n\nTo confirm a suggestion the user chose, send its `suggestion_id` instead of `address` — the service resolves it to cleansed components and verifies those in one call (consumes a Loqate credit).",
        "requestBody": {
          "required": true,
          "content": { "application/json": {
            "schema": { "$ref": "#/components/schemas/AddressRequest" },
            "examples": {
              "freeform": { "summary": "Free-form address", "value": { "address": "125 Summer St, Boston, MA 02110, US", "policy": "standard" } },
              "detectCountry": { "summary": "Guess a missing country", "value": { "address": "10 Downing St, London SW1A 2AA", "detect_country": true } },
              "suggest": { "summary": "Offer alternatives when not accepted or below the floor", "value": { "address": "10 downin st london", "country": "GB", "suggest": true, "suggest_limit": 5 } },
              "suggestBelow": { "summary": "Raise the suggestion confidence floor", "value": { "address": "marsh wall, E14 9TN", "country": "GB", "suggest": true, "suggest_below": 0.9 } },
              "confirmSuggestion": { "summary": "Confirm a suggestion the user chose", "value": { "suggestion_id": "GB|RM|B|55782678" } }
            }
          } }
        },
        "responses": {
          "200": { "description": "Verification result", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AddressResult" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamError" }
        }
      }
    },
    "/v1/verify/email": {
      "post": {
        "tags": ["verify"],
        "summary": "Verify an email address",
        "operationId": "verifyEmail",
        "requestBody": {
          "required": true,
          "content": { "application/json": {
            "schema": { "$ref": "#/components/schemas/EmailRequest" },
            "examples": { "basic": { "value": { "email": "user@example.com" } } }
          } }
        },
        "responses": {
          "200": { "description": "Verification result", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EmailResult" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamError" }
        }
      }
    },
    "/v1/verify/phone": {
      "post": {
        "tags": ["verify"],
        "summary": "Verify a phone number",
        "operationId": "verifyPhone",
        "requestBody": {
          "required": true,
          "content": { "application/json": {
            "schema": { "$ref": "#/components/schemas/PhoneRequest" },
            "examples": { "e164": { "value": { "phone": "+442071234567" } } }
          } }
        },
        "responses": {
          "200": { "description": "Verification result", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PhoneResult" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamError" }
        }
      }
    },
    "/v1/verify/contact": {
      "post": {
        "tags": ["verify"],
        "summary": "Verify address, email, and/or phone together",
        "operationId": "verifyContact",
        "description": "Verify any combination of address, email, and phone in one call. Returns each field's result plus an overall recommendation (the most conservative of the fields provided). `detect_country` and `suggest` apply to the address component only.",
        "requestBody": {
          "required": true,
          "content": { "application/json": {
            "schema": { "$ref": "#/components/schemas/ContactRequest" },
            "examples": { "all": { "value": { "address": "10 Downing St, London, GB", "email": "pm@gov.uk", "phone": "+442071234567", "policy": "shipping" } } }
          } }
        },
        "responses": {
          "200": { "description": "Combined result", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VerifyResult" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamError" }
        }
      }
    },
    "/v1/address/{id}": {
      "get": {
        "tags": ["verify"],
        "summary": "Resolve a suggestion id to a cleansed address",
        "operationId": "retrieveAddress",
        "description": "Resolve an id from `suggestions.items[].id` into its full cleansed address components.\n\nUse this when you need the address itself — to fill a form or render a confirmation screen. To *verify* a chosen suggestion instead, pass `suggestion_id` to `POST /v1/verify/address`, which resolves and verifies in one call.\n\n**This consumes a Loqate credit** (unlike the suggestion search, which does not). Resolve only the one suggestion the user actually chose. The response is reference data, not a decision.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" },
            "description": "Suggestion id, e.g. GB|RM|B|55782678. Not stable over time.",
            "example": "GB|RM|B|55782678" }
        ],
        "responses": {
          "200": { "description": "Cleansed address", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RetrieveResult" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/SuggestionNotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamError" }
        }
      }
    },
    "/v1/policies": {
      "get": {
        "tags": ["policies"],
        "summary": "List decisioning policies",
        "operationId": "listPolicies",
        "responses": {
          "200": { "description": "Policies", "content": { "application/json": { "schema": {
            "type": "object", "properties": { "policies": { "type": "array", "items": { "$ref": "#/components/schemas/Policy" } } }
          } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/policies/{name}": {
      "get": {
        "tags": ["policies"],
        "summary": "Show one policy",
        "operationId": "showPolicy",
        "parameters": [ { "name": "name", "in": "path", "required": true, "schema": { "type": "string", "enum": ["strict", "shipping", "standard", "permissive"] } } ],
        "responses": {
          "200": { "description": "Policy", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Policy" } } } },
          "404": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your Loqate API key, presented as a bearer token: `Authorization: Bearer <LOQATE_API_KEY>`. Don't have one? Get a key at https://account.loqate.com or purchase a plan at https://www.loqate.com."
      }
    },
    "responses": {
      "BadRequest": { "description": "Invalid input or malformed JSON", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": true, "code": "INVALID_INPUT", "message": "address is required" } } } },
      "Unauthorized": { "description": "Missing or invalid Loqate API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": true, "code": "NO_API_KEY", "message": "no Loqate API key: set Authorization: Bearer <key>" } } } },
      "Forbidden": { "description": "Custom verify endpoint rejected (disabled on this server)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": true, "code": "CUSTOM_ENDPOINT_DISABLED", "message": "Custom verify endpoints are disabled on this server." } } } },
      "SuggestionNotFound": { "description": "The suggestion id resolves to no address. Loqate ids change over time, so search again rather than retrying the same id.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": true, "code": "SUGGESTION_NOT_FOUND", "message": "no address found for suggestion id \"GB|OLD|1\" — ids are not stable over time, so search again to get a current one" } } } },
      "RateLimited": { "description": "Per-IP rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": true, "code": "RATE_LIMITED", "message": "Too many requests" } } } },
      "UpstreamError": { "description": "Loqate upstream call failed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": true, "code": "API_ERROR", "message": "Address verification failed" } } } }
    },
    "schemas": {
      "Recommendation": { "type": "string", "enum": ["accept", "review", "reject"], "description": "Policy-driven decision." },
      "AddressRequest": {
        "type": "object",
        "description": "Free-form `address`, or structured fields. Any Loqate input field (address2…address8, organization, building, premise, etc.) may also be supplied.",
        "additionalProperties": true,
        "properties": {
          "address": { "type": "string", "description": "Full free-form address" },
          "suggestion_id": { "type": "string", "description": "Id of a suggestion previously returned in `suggestions.items[].id`. The service retrieves that address's cleansed components from Loqate and verifies **those**, which is how you confirm a suggestion the user chose — higher fidelity than re-sending the display line, because nothing is re-parsed. Use INSTEAD OF `address`, not alongside it (supplying both is a 400). **Consumes a Loqate credit.** Ids are not stable over time; a stale one returns 404 SUGGESTION_NOT_FOUND and you must search again rather than retry." },
          "locality": { "type": "string", "description": "City or town" },
          "admin_area": { "type": "string", "description": "State or province" },
          "postcode": { "type": "string" },
          "country": { "type": "string", "description": "ISO 2-letter country code" },
          "detect_country": { "type": "boolean", "default": false, "description": "When true and no country is supplied, guess it from the address and flag the guess (country_guessed) in the result. Address-only." },
          "suggest": { "type": "boolean", "default": false, "description": "When true, look up alternative addresses suggested by Loqate and return them under `suggestions`. The lookup runs when the address is not accepted (review or reject) OR when it is accepted below the confidence floor (see `suggest_below`). **Requires the Loqate address-suggestion feature to be enabled on your account** — it is licensed separately from address verification. Without it the verification still succeeds and the problem is reported in `suggestions.error`." },
          "suggest_limit": { "type": "integer", "minimum": 1, "maximum": 10, "default": 5, "description": "Maximum number of suggestions to return. Requires `suggest`." },
          "suggest_below": { "type": "number", "minimum": 0, "maximum": 1, "description": "Confidence floor for suggestions. An accepted address scoring below it still gets a lookup — a policy accepts from its own `min_confidence`, so an accepted address is not necessarily a confident one. Omit to use the policy's `suggest_below_confidence` (standard 0.85); 0 disables the floor so only review/reject trigger a lookup." },
          "policy": { "type": "string", "enum": ["strict", "shipping", "standard", "permissive"], "default": "standard" },
          "key": { "type": "string", "description": "Loqate API key (alternative to the Authorization header)" },
          "options": { "type": "object", "description": "Loqate API options (e.g. GeoCode, ServerOptions.OutputCasing)." }
        }
      },
      "EmailRequest": {
        "type": "object", "required": ["email"],
        "properties": {
          "email": { "type": "string", "format": "email" },
          "policy": { "type": "string", "enum": ["strict", "shipping", "standard", "permissive"] },
          "key": { "type": "string" }
        }
      },
      "PhoneRequest": {
        "type": "object", "required": ["phone"],
        "properties": {
          "phone": { "type": "string", "description": "E.164 format preferred, e.g. +442071234567" },
          "country": { "type": "string", "description": "ISO 2-letter country code (helps with national-format numbers)" },
          "policy": { "type": "string", "enum": ["strict", "shipping", "standard", "permissive"] },
          "key": { "type": "string" }
        }
      },
      "ContactRequest": {
        "type": "object",
        "description": "At least one of address, email, or phone must be provided.",
        "additionalProperties": true,
        "properties": {
          "address": { "type": "string" },
          "suggestion_id": { "type": "string", "description": "Id of a suggestion previously returned in `suggestions.items[].id`. The service retrieves that address's cleansed components from Loqate and verifies **those**, which is how you confirm a suggestion the user chose — higher fidelity than re-sending the display line, because nothing is re-parsed. Use INSTEAD OF `address`, not alongside it (supplying both is a 400). **Consumes a Loqate credit.** Ids are not stable over time; a stale one returns 404 SUGGESTION_NOT_FOUND and you must search again rather than retry." },
          "locality": { "type": "string" },
          "admin_area": { "type": "string" },
          "postcode": { "type": "string" },
          "country": { "type": "string" },
          "detect_country": { "type": "boolean", "default": false, "description": "Address-only country guess; does not affect phone or email." },
          "suggest": { "type": "boolean", "default": false, "description": "Address-only alternative lookup, run when the address is not accepted or is accepted below the confidence floor; does not affect phone or email." },
          "suggest_limit": { "type": "integer", "minimum": 1, "maximum": 10, "default": 5, "description": "Maximum number of suggestions to return. Requires `suggest`." },
          "suggest_below": { "type": "number", "minimum": 0, "maximum": 1, "description": "Confidence floor for suggestions; overrides the policy's `suggest_below_confidence`. 0 disables the floor. Address-only." },
          "email": { "type": "string", "format": "email" },
          "phone": { "type": "string" },
          "policy": { "type": "string", "enum": ["strict", "shipping", "standard", "permissive"] },
          "key": { "type": "string" },
          "options": { "type": "object" }
        }
      },
      "AddressResult": {
        "type": "object",
        "properties": {
          "verified_address": { "type": "string" },
          "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
          "recommendation": { "$ref": "#/components/schemas/Recommendation" },
          "match_level": { "type": "string", "description": "delivery_point, premise, street, locality, administrative_area, or none" },
          "avc": { "type": "string", "description": "Loqate Address Verification Code" },
          "fields": { "type": "object", "description": "Verified/standardized address fields returned by Loqate" },
          "country_guessed": { "type": "boolean", "description": "Present only when detect_country was enabled" },
          "detected_country": { "type": "string" },
          "country_source": { "type": "string", "description": "\"detected\" when guessed" },
          "country_confidence": { "type": "number", "minimum": 0, "maximum": 1 },
          "country_detection": { "type": "string", "description": "\"low_confidence\" when detection was inconclusive" },
          "resolved_from": { "$ref": "#/components/schemas/ResolvedSuggestion" },
          "suggestions": { "$ref": "#/components/schemas/Suggestions" }
        },
        "example": { "verified_address": "10 Downing Street, London, SW1A 2AA", "confidence": 0.95, "recommendation": "accept", "match_level": "premise", "avc": "V44-I44-P6-100", "country_guessed": true, "detected_country": "GB", "country_source": "detected", "country_confidence": 0.88 }
      },
      "ResolvedSuggestion": {
        "type": "object",
        "description": "Present when the verified address was resolved from a `suggestion_id` rather than supplied as text. Records what the id resolved to, so the result is auditable without paying to retrieve it again.",
        "properties": {
          "suggestion_id": { "type": "string" },
          "address": { "type": "string", "description": "Single-line form of what the id resolved to" },
          "components": { "$ref": "#/components/schemas/RetrievedAddress" }
        }
      },
      "RetrievedAddress": {
        "type": "object",
        "description": "A cleansed, component-split address from Loqate's reference data. These are the fields sent to verification when a `suggestion_id` is used — an organisation name lands in `company`, never in `street`.",
        "properties": {
          "id": { "type": "string", "description": "Not stable over time — do not persist" },
          "company": { "type": "string" },
          "department": { "type": "string" },
          "sub_building": { "type": "string" },
          "building_name": { "type": "string" },
          "building_number": { "type": "string" },
          "street": { "type": "string" },
          "secondary_street": { "type": "string" },
          "district": { "type": "string" },
          "neighbourhood": { "type": "string" },
          "block": { "type": "string" },
          "city": { "type": "string" },
          "po_box_number": { "type": "string" },
          "province": { "type": "string" },
          "province_name": { "type": "string" },
          "province_code": { "type": "string" },
          "postal_code": { "type": "string" },
          "country_name": { "type": "string" },
          "country_iso2": { "type": "string" },
          "country_iso3": { "type": "string" },
          "line1": { "type": "string" }, "line2": { "type": "string" }, "line3": { "type": "string" },
          "line4": { "type": "string" }, "line5": { "type": "string" },
          "label": { "type": "string", "description": "Loqate's own multi-line printed form" },
          "language": { "type": "string" },
          "type": { "type": "string" }
        }
      },
      "RetrieveResult": {
        "type": "object",
        "description": "Result of resolving a suggestion id. This is reference data, NOT a verification — there is no confidence, match level, or recommendation.",
        "properties": {
          "suggestion_id": { "type": "string" },
          "address": { "type": "string", "description": "Single-line form, ready to display" },
          "components": { "$ref": "#/components/schemas/RetrievedAddress" },
          "note": { "type": "string", "description": "Restates that the address is unverified and that the call consumed a credit" }
        }
      },
      "Suggestions": {
        "type": "object",
        "description": "Outcome of the opt-in Loqate address-suggestion lookup. Present only when `suggest` was set — including when no lookup ran, so a client can tell \"not needed\" from \"not requested\". Suggestions are additive: a failed lookup is reported in `error` and never changes the confidence or recommendation.\n\n**Closing the loop.** A suggestion is a candidate, not a verdict — items carry no confidence, match level or AVC. Offer `items[].address` to whoever can choose, then POST the chosen line back to this endpoint **without** `suggest`; that second response is the decision of record. Keep the same `country`. If it still returns review or reject the record needs a human. Do not persist `id`.",
        "properties": {
          "requested": { "type": "boolean", "description": "Always true when this object is present" },
          "triggered": { "type": "boolean", "description": "Whether a suggestion lookup was actually issued" },
          "reason": { "type": "string", "description": "Why the lookup ran or was skipped, e.g. \"recommendation=review\", \"accepted but confidence 0.55 is below the 0.85 floor\", or \"not needed: accepted at confidence 0.95 (at or above the 0.85 floor)\"" },
          "floor": { "type": "number", "minimum": 0, "maximum": 1, "description": "The resolved confidence floor applied to this call: an accepted address below it still gets suggestions. 0 means the floor was disabled. Resolution order is suggest_below > the policy's suggest_below_confidence > the shipped default of 0.85." },
          "source": { "type": "string", "description": "\"loqate\" when a lookup ran" },
          "count": { "type": "integer" },
          "items": { "type": "array", "items": { "$ref": "#/components/schemas/Suggestion" } },
          "error": { "type": "string", "description": "Present if the lookup failed. The verification result above is still valid. A licensing or unknown-key message here usually means the Loqate address-suggestion feature is not enabled on the account — it is licensed separately from address verification." }
        },
        "example": { "requested": true, "triggered": true, "reason": "accepted but confidence 0.55 is below the 0.85 floor", "floor": 0.85, "source": "loqate", "count": 2, "items": [ { "id": "GB|RM|A|52509479", "type": "Address", "text": "10 Downing Street", "description": "London, SW1A 2AA", "address": "10 Downing Street, London, SW1A 2AA" } ] }
      },
      "Suggestion": {
        "type": "object",
        "description": "One candidate address suggested by Loqate. Verify `address` in a second call before treating it as good. Items arrive in Loqate's own relevance order and are not re-ranked; the match runs across the whole record including organisation names, so leaving a city in the `address` text rather than using the `country` field can surface businesses whose name contains that city.",
        "properties": {
          "id": { "type": "string", "description": "Loqate-assigned suggestion id. Not stable over time — do not persist it." },
          "type": { "type": "string", "description": "Address, Street, Postcode, BuildingName, Building, or Container" },
          "text": { "type": "string", "description": "First half of the address, e.g. \"10 Downing Street\"" },
          "description": { "type": "string", "description": "Second half of the address, e.g. \"London, SW1A 2AA\"" },
          "address": { "type": "string", "description": "text and description joined — the single line to display to a user, and the value to send back in a follow-up verify call once chosen" },
          "expandable": { "type": "boolean", "description": "True when this is a container (street, postcode, building) holding multiple addresses rather than a deliverable address. Do not re-verify it as-is — prompt for the missing part (e.g. the house number) and verify the completed address" }
        }
      },
      "EmailResult": {
        "type": "object",
        "properties": {
          "email": { "type": "string" },
          "verified": { "type": "boolean" },
          "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
          "recommendation": { "$ref": "#/components/schemas/Recommendation" },
          "risk": { "type": "string", "description": "low, medium, or high" },
          "response_code": { "type": "string" },
          "flags": { "type": "array", "items": { "type": "string" } },
          "fields": { "type": "object" }
        }
      },
      "PhoneResult": {
        "type": "object",
        "properties": {
          "phone": { "type": "string" },
          "verified": { "type": "boolean" },
          "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
          "recommendation": { "$ref": "#/components/schemas/Recommendation" },
          "number_type": { "type": "string", "description": "mobile, landline, voip, etc." },
          "network_name": { "type": "string" },
          "network_country": { "type": "string" },
          "national_format": { "type": "string" },
          "fields": { "type": "object" }
        }
      },
      "VerifyResult": {
        "type": "object",
        "properties": {
          "input": { "type": "object", "properties": { "address": { "type": "string" }, "email": { "type": "string" }, "phone": { "type": "string" } } },
          "address": { "$ref": "#/components/schemas/AddressResult" },
          "email": { "$ref": "#/components/schemas/EmailResult" },
          "phone": { "$ref": "#/components/schemas/PhoneResult" },
          "overall": { "type": "object", "properties": { "recommendation": { "$ref": "#/components/schemas/Recommendation" }, "confidence": { "type": "number", "minimum": 0, "maximum": 1 } } },
          "policy": { "type": "string" }
        }
      },
      "Policy": {
        "type": "object",
        "description": "A decisioning policy. Thresholds control accept/review/reject.",
        "additionalProperties": true,
        "properties": {
          "name": { "type": "string" },
          "description": { "type": "string" },
          "address": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "min_confidence": { "type": "number", "minimum": 0, "maximum": 1, "description": "Confidence at or above which an address is accepted." },
              "min_match_level": { "type": "string", "description": "delivery_point, premise, street, locality, administrative_area, or none. Below this level the address is rejected." },
              "reject_verification_status": { "type": "array", "items": { "type": "string" }, "description": "AVC verification statuses rejected outright (V, P, A, R, U)." },
              "suggest_below_confidence": { "type": "number", "minimum": 0, "maximum": 1, "description": "Confidence below which address suggestions are offered even when the policy accepts the address. NOT an acceptance threshold — it never changes accept/review/reject, it only decides when an opt-in `suggest` lookup is worth making. Built-in values: strict 0.90, shipping 0.85, standard 0.85, permissive 0.70. 0 disables the floor." }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error", "code", "message"],
        "properties": {
          "error": { "type": "boolean", "const": true },
          "code": { "type": "string", "description": "INVALID_INPUT, NO_API_KEY, INVALID_POLICY, SUGGESTION_NOT_FOUND, CUSTOM_ENDPOINT_DISABLED, RATE_LIMITED, API_ERROR, or INTERNAL_ERROR" },
          "message": { "type": "string" }
        }
      }
    }
  }
}
