{
  "openapi": "3.1.0",
  "info": {
    "title": "UGP Registry Service",
    "version": "2026-06-25",
    "description": "Canonical REST interface for the UGP Registry service (`dev.ugp.registry`). A registry is a free, open directory that resolves a nonprofit by name, EIN, or website to its `/.well-known/ucp`, so a platform can then discover and verify the authoritative profile directly.\n\n**Endpoint Resolution:** This spec defines operations only. The base URL MUST be obtained from a registry operator's discovery profile at `/.well-known/ucp` under `services[\"dev.ugp.registry\"][transport=rest].endpoint`. `ugp.dev` operates the canonical free registry; anyone MAY operate another. Registries are interchangeable: a platform may query any registry with the same contract.\n\n**Trust boundary:** Search results are pointers, not authority. A caller MUST resolve and verify the returned `well_known_url` (signature, capabilities) before transacting. A stale or malicious registry entry can mislead a search result but cannot misdirect funds, because settlement always flows through verified discovery."
  },
  "servers": [
    {
      "url": "{endpoint}",
      "description": "Operator-hosted registry endpoint from UGP discovery profile",
      "variables": {
        "endpoint": {
          "default": "https://ugp.dev/api/registry/v1",
          "description": "Obtain from /.well-known/ucp → services[\"dev.ugp.registry\"][transport=rest].endpoint"
        }
      }
    }
  ],
  "paths": {
    "/search": {
      "get": {
        "operationId": "search",
        "summary": "Search the Registry",
        "description": "Resolve nonprofits by name, EIN, or website. Returns ranked pointers to authoritative `/.well-known/ucp` documents. This operation is read-only and MAY be offered unauthenticated.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text query matched against name, legal name, website, and domain."
          },
          {
            "name": "ein",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^\\d{2}-\\d{7}$"
            },
            "description": "Exact EIN match in NN-NNNNNNN format."
          },
          {
            "name": "website",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Website or domain to resolve."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 10
            },
            "description": "Maximum number of results. Implementations MAY clamp."
          },
          {
            "$ref": "#/components/parameters/request_id"
          },
          {
            "$ref": "#/components/parameters/ucp_agent"
          },
          {
            "$ref": "#/components/parameters/accept"
          }
        ],
        "responses": {
          "200": {
            "description": "Ranked search results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/search_response"
                }
              }
            }
          }
        }
      }
    },
    "/index": {
      "post": {
        "operationId": "index",
        "summary": "Index a Well-Known",
        "description": "Submit a nonprofit's `/.well-known/ucp` for indexing. The registry fetches and parses the document, verifies what it can, and derives a search entry. Typically called by the provider that publishes or edits the profile (e.g. when a nonprofit adds the giving service), or by the nonprofit itself when self-hosting. Idempotent on `well_known_url`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/authorization"
          },
          {
            "$ref": "#/components/parameters/x_api_key"
          },
          {
            "$ref": "#/components/parameters/idempotency_key"
          },
          {
            "$ref": "#/components/parameters/request_id"
          },
          {
            "$ref": "#/components/parameters/ucp_agent"
          },
          {
            "$ref": "#/components/parameters/content_type"
          },
          {
            "$ref": "#/components/parameters/accept"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/index_request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Index outcome",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/index_response"
                }
              }
            }
          }
        }
      }
    },
    "/index/{domain}": {
      "parameters": [
        {
          "name": "domain",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "The domain that hosts the `/.well-known/ucp` to deregister."
        }
      ],
      "delete": {
        "operationId": "remove",
        "summary": "Deregister a Domain",
        "description": "Remove a domain's entry from the registry. Also happens automatically when a re-crawl finds the `/.well-known/ucp` returns 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/authorization"
          },
          {
            "$ref": "#/components/parameters/x_api_key"
          },
          {
            "$ref": "#/components/parameters/request_id"
          },
          {
            "$ref": "#/components/parameters/ucp_agent"
          }
        ],
        "responses": {
          "200": {
            "description": "Deregistration outcome",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/index_response"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "authorization": {
        "name": "Authorization",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "OAuth bearer token. Required for write operations (`index`, `remove`) when the registry restricts who may submit."
      },
      "x_api_key": {
        "name": "X-API-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Reusable API key for write operations, as an alternative to OAuth."
      },
      "idempotency_key": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "Ensures duplicate index submissions don't queue duplicate work on retry."
      },
      "request_id": {
        "name": "Request-Id",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "For tracing requests across network layers and components."
      },
      "ucp_agent": {
        "name": "UCP-Agent",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Identifies the calling agent by its profile URI using RFC 8941 Dictionary syntax. Format: profile=\"https://example.com/.well-known/ucp\"."
      },
      "content_type": {
        "name": "Content-Type",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Representation metadata for the request body."
      },
      "accept": {
        "name": "Accept",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Content negotiation. The client tells the server what formats it understands."
      }
    },
    "schemas": {
      "search_response": {
        "$ref": "https://ugp.dev/2026-06-25/schemas/registry/search.json#/$defs/search_response"
      },
      "index_request": {
        "$ref": "https://ugp.dev/2026-06-25/schemas/registry/search.json#/$defs/index_request"
      },
      "index_response": {
        "$ref": "https://ugp.dev/2026-06-25/schemas/registry/search.json#/$defs/index_response"
      }
    }
  }
}
