{
  "openapi": "3.1.0",
  "info": {
    "title": "Divi Name Service API",
    "version": "1.0.0",
    "summary": "Names for Divi addresses: resolve, list, price and register DVNS1 records.",
    "description": "A name points to a Divi address. Records live on the Divi chain (DVNS1, an OP_RETURN text record plus a fee paid to the registry address); this API indexes them and builds the payment for you. Reads need no key. Writes are ordinary Divi payments your wallet signs: POST /build, pay `send.amount` to `send.to` with `send.memo` as the OP_RETURN, spending only `send.from`'s coins, then POST /notify. Every endpoint takes `network=mainnet|testnet` (mainnet is the default). Errors are `{\"error\": \"...\"}` with a 4xx/5xx status. CORS is open to any origin. Guide: https://name.divi.cx/docs/ — agents: https://name.divi.cx/llms.txt"
  },
  "servers": [{ "url": "https://name.divi.cx" }],
  "tags": [
    { "name": "read", "description": "Look names up. No key, no cost." },
    { "name": "write", "description": "Register, update and delete names by paying from a Divi wallet." }
  ],
  "paths": {
    "/api/v1/info": {
      "get": {
        "tags": ["read"],
        "summary": "Registry address, sync height, fees and reserved names",
        "parameters": [{ "$ref": "#/components/parameters/network" }],
        "responses": {
          "200": { "description": "Service info", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Info" } } } },
          "503": { "$ref": "#/components/responses/NotLaunched" }
        }
      }
    },
    "/api/v1/resolve/{name}": {
      "get": {
        "tags": ["read"],
        "summary": "Resolve a name to its Divi address",
        "description": "Accepts `bert`, `bert@siegeworlds`, and `@handle` (read as `handle@telegram`). Case-insensitive. Show the user the name AND the returned `divi` address before sending.",
        "parameters": [
          { "name": "name", "in": "path", "required": true, "schema": { "type": "string" }, "example": "knight@siegeworlds" },
          { "$ref": "#/components/parameters/network" }
        ],
        "responses": {
          "200": { "description": "The name's record", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Entry" } } } },
          "400": { "$ref": "#/components/responses/Bad" },
          "404": {
            "description": "Not registered. `pending` lists registrations waiting to confirm (they do not resolve yet).",
            "content": { "application/json": { "schema": {
              "type": "object",
              "properties": {
                "error": { "type": "string" },
                "name": { "type": "string" },
                "pending": { "type": "array", "items": { "$ref": "#/components/schemas/Pending" } }
              }
            } } }
          }
        }
      }
    },
    "/api/v1/names": {
      "get": {
        "tags": ["read"],
        "summary": "List and search names",
        "description": "Filter by namespace (`ns=siegeworlds` lists every gamer tag of that game), by address (every name pointing at or owned by an address: use this to find all of a player's names across games), by kind, or by substring.",
        "parameters": [
          { "$ref": "#/components/parameters/network" },
          { "name": "ns", "in": "query", "schema": { "type": "string" }, "description": "Only tags in this namespace" },
          { "name": "top", "in": "query", "schema": { "type": "boolean" }, "description": "Only top-level names" },
          { "name": "q", "in": "query", "schema": { "type": "string" }, "description": "Substring of the name" },
          { "name": "kind", "in": "query", "schema": { "type": "string" }, "description": "person, org, game, sidechain, gamertag, telegram, …" },
          { "name": "address", "in": "query", "schema": { "type": "string" }, "description": "Names whose divi or owner is this address" },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 100, "maximum": 1000 } },
          { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0 } }
        ],
        "responses": {
          "200": { "description": "A page of names", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "network": { "type": "string" },
              "total": { "type": "integer" },
              "names": { "type": "array", "items": { "$ref": "#/components/schemas/Entry" } }
            }
          } } } }
        }
      }
    },
    "/api/v1/names/{name}/history": {
      "get": {
        "tags": ["read"],
        "summary": "Every record ever sent for one name, newest first",
        "parameters": [
          { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } },
          { "$ref": "#/components/parameters/network" }
        ],
        "responses": {
          "200": { "description": "History", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HistoryList" } } } }
        }
      }
    },
    "/api/v1/history": {
      "get": {
        "tags": ["read"],
        "summary": "Recent registry transactions, applied and rejected, newest first",
        "parameters": [
          { "$ref": "#/components/parameters/network" },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 1000 } }
        ],
        "responses": {
          "200": { "description": "History", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HistoryList" } } } }
        }
      }
    },
    "/api/v1/pending": {
      "get": {
        "tags": ["read"],
        "summary": "Broadcast records waiting to confirm",
        "parameters": [{ "$ref": "#/components/parameters/network" }],
        "responses": {
          "200": { "description": "Pending records", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "network": { "type": "string" },
              "pending": { "type": "array", "items": { "$ref": "#/components/schemas/Pending" } }
            }
          } } } }
        }
      }
    },
    "/api/v1/fee": {
      "get": {
        "tags": ["read"],
        "summary": "Price a name and say whether it is free",
        "parameters": [
          { "$ref": "#/components/parameters/network" },
          { "name": "name", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "op", "in": "query", "schema": { "type": "string", "enum": ["register", "update", "delete"], "default": "register" } }
        ],
        "responses": {
          "200": { "description": "Price and availability", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "network": { "type": "string" },
              "name": { "type": "string" },
              "op": { "type": "string" },
              "fee": { "type": "string", "description": "DIVI, decimal" },
              "fee_sat": { "type": "integer" },
              "registered": { "type": "boolean" },
              "available": { "type": "boolean" },
              "reserved": { "type": "boolean" },
              "namespace_missing": { "type": "boolean", "description": "A tag whose namespace is not registered" }
            }
          } } } },
          "400": { "$ref": "#/components/responses/Bad" }
        }
      }
    },
    "/api/v1/build": {
      "post": {
        "tags": ["write"],
        "summary": "Build the payment that registers, updates or deletes a name",
        "description": "Checks the record against the current registry first and answers 409 if it would be rejected, so nobody pays for nothing. On success, pay `send.amount` DIVI to `send.to` with `send.memo` as the OP_RETURN text, spending only coins from `send.from` (the payer; input 0 must be theirs). Wallets that build raw transactions can use `outputs` instead. Then POST /notify with the txid.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BuildRequest" },
            "examples": {
              "person": { "value": { "network": "testnet", "op": "register", "name": "bertshuler", "payer": "y2wgejXn4xeS95Jw5vdHzyQkfPjAihXYBU", "fields": { "kind": "person" } } },
              "game": { "value": { "network": "testnet", "op": "register", "name": "siegeworlds", "payer": "y…", "fields": { "kind": "game", "signer": "y…server key…", "site": "https://siegeworlds.example" } } },
              "gamertag": { "value": { "network": "testnet", "op": "register", "name": "knight@siegeworlds", "payer": "y…player…", "auth": "H…base64 signature from the game server…", "fields": { "kind": "gamertag" } } }
            }
          } }
        },
        "responses": {
          "200": { "description": "The payment to make", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Built" } } } },
          "400": { "$ref": "#/components/responses/Bad" },
          "409": {
            "description": "The record would be rejected (name taken, not the owner, missing auth, …). For a tag, `auth_message` is the exact text the namespace's owner or signer must sign.",
            "content": { "application/json": { "schema": {
              "type": "object",
              "properties": { "error": { "type": "string" }, "auth_message": { "type": ["string", "null"] } }
            } } }
          }
        }
      }
    },
    "/api/v1/notify": {
      "post": {
        "tags": ["write"],
        "summary": "Tell the service about a record you just broadcast",
        "description": "Optional but recommended: the name shows as pending (with any problem it will hit) until it confirms. Pending records never resolve.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object",
            "required": ["txid"],
            "properties": { "network": { "type": "string", "enum": ["mainnet", "testnet"] }, "txid": { "type": "string", "pattern": "^[0-9a-fA-F]{64}$" } }
          } } }
        },
        "responses": {
          "200": { "description": "`status` is `pending` (with the Pending fields) or `confirmed`", "content": { "application/json": { "schema": {
            "allOf": [{ "$ref": "#/components/schemas/Pending" }, { "type": "object", "properties": { "status": { "type": "string", "enum": ["pending", "confirmed"] } } }]
          } } } },
          "400": { "$ref": "#/components/responses/Bad" },
          "404": { "description": "The transaction is not known to the Divi node" }
        }
      }
    },
    "/api/v1/auth/telegram": {
      "post": {
        "tags": ["write"],
        "summary": "Get the signature that lets a Telegram user register handle@telegram",
        "description": "Call from a Telegram Mini App with its `initData`. The service checks Telegram's signature, reads the user's @username and signs `DVNS1 auth <handle>@telegram <owner> <divi>` with the telegram namespace's signer key. Pass the returned `auth` to /build.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object",
            "required": ["init_data", "payer", "divi"],
            "properties": {
              "network": { "type": "string", "enum": ["mainnet", "testnet"] },
              "init_data": { "type": "string", "description": "Telegram.WebApp.initData, verbatim" },
              "payer": { "type": "string" },
              "divi": { "type": "string" },
              "owner": { "type": "string", "description": "Defaults to payer" }
            }
          } } }
        },
        "responses": {
          "200": { "description": "The auth signature", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "network": { "type": "string" }, "name": { "type": "string" }, "owner": { "type": "string" }, "divi": { "type": "string" },
              "auth_message": { "type": "string" }, "auth": { "type": "string" }, "signer": { "type": "string" }
            }
          } } } },
          "401": { "description": "Telegram's initData did not verify or is too old" },
          "503": { "description": "Telegram names are not set up on this server" }
        }
      }
    },
    "/api/v1/health": {
      "get": { "summary": "Liveness", "responses": { "200": { "description": "`{\"ok\": true}`" } } }
    }
  },
  "components": {
    "parameters": {
      "network": { "name": "network", "in": "query", "schema": { "type": "string", "enum": ["mainnet", "testnet"], "default": "mainnet" } }
    },
    "responses": {
      "Bad": { "description": "Bad input", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotLaunched": { "description": "Not launched on this network yet", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    },
    "schemas": {
      "Error": { "type": "object", "properties": { "error": { "type": "string" } } },
      "Entry": {
        "type": "object",
        "properties": {
          "name": { "type": "string", "example": "knight@siegeworlds" },
          "divi": { "type": "string", "description": "Send Divi here" },
          "owner": { "type": "string", "description": "Controls the name" },
          "fields": { "type": "object", "additionalProperties": { "type": "string" }, "description": "kind, site, signer, policy, chain.<id>, and any other key", "example": { "kind": "gamertag", "chain.siegeworlds": "player-48213" } },
          "registered": { "type": "object", "description": "{height, txid} of the register" },
          "updated": { "type": "object", "description": "{height, txid} of the latest record" },
          "network": { "type": "string" }
        }
      },
      "Pending": {
        "type": "object",
        "properties": {
          "txid": { "type": "string" },
          "op": { "type": ["string", "null"] },
          "name": { "type": ["string", "null"] },
          "payer": { "type": ["string", "null"] },
          "paid_sat": { "type": "integer" },
          "problem": { "type": ["string", "null"], "description": "null if it will apply when it confirms; otherwise why it will be rejected" }
        }
      },
      "HistoryList": {
        "type": "object",
        "properties": {
          "network": { "type": "string" },
          "history": { "type": "array", "items": {
            "type": "object",
            "properties": {
              "txid": { "type": "string" }, "height": { "type": "integer" }, "op": { "type": ["string", "null"] },
              "name": { "type": ["string", "null"] }, "payer": { "type": ["string", "null"] }, "paid_sat": { "type": "integer" },
              "status": { "type": "string", "enum": ["applied", "rejected"] }, "reason": { "type": "string" }
            }
          } }
        }
      },
      "Info": {
        "type": "object",
        "properties": {
          "protocol": { "type": "string", "const": "DVNS1" },
          "network": { "type": "string" },
          "registry": { "type": "string", "description": "Fees are paid here" },
          "height": { "type": "integer" },
          "names": { "type": "integer" },
          "pending": { "type": "integer" },
          "fees_divi": { "type": "object", "additionalProperties": { "type": "string" } },
          "reserved": { "type": "array", "items": { "type": "string" } },
          "max_record_bytes": { "type": "integer" },
          "telegram_signer": { "type": ["string", "null"] }
        }
      },
      "BuildRequest": {
        "type": "object",
        "required": ["name", "payer"],
        "properties": {
          "network": { "type": "string", "enum": ["mainnet", "testnet"] },
          "op": { "type": "string", "enum": ["register", "update", "delete"], "default": "register" },
          "name": { "type": "string" },
          "payer": { "type": "string", "description": "The address that pays and signs input 0. For update/delete it must own the name (or, for a tag, own or be signer of the namespace)." },
          "divi": { "type": "string", "description": "Where the name sends Divi. Required on register." },
          "owner": { "type": "string", "description": "Who controls the name. Defaults to payer on register; on update, set it to transfer." },
          "auth": { "type": "string", "description": "For tag@ns: the namespace owner's or signer's signmessage signature (base64) over `DVNS1 auth <name> <owner> <divi>`" },
          "fields": { "type": "object", "additionalProperties": { "type": "string" }, "description": "kind, site (https), signer, policy (open|closed), chain.<id>, any other key. Update replaces the whole record: resend fields you want to keep." }
        }
      },
      "Built": {
        "type": "object",
        "properties": {
          "network": { "type": "string" },
          "op": { "type": "string" },
          "name": { "type": "string" },
          "payer": { "type": "string" },
          "record": { "type": "string", "description": "The DVNS1 text" },
          "payload_hex": { "type": "string" },
          "registry": { "type": "string" },
          "fee": { "type": "string" },
          "fee_sat": { "type": "integer" },
          "send": {
            "type": "object",
            "description": "The whole payment as one ordinary send with a memo",
            "properties": {
              "to": { "type": "string" }, "amount": { "type": "string" }, "amount_sat": { "type": "integer" },
              "memo": { "type": "string", "description": "OP_RETURN text" }, "from": { "type": "string", "description": "Spend only this address's coins" }
            }
          },
          "outputs": { "type": "array", "description": "For raw-transaction builders: the OP_RETURN output and the fee output. Add your own inputs (input 0 from payer), change and network fee.", "items": {
            "type": "object", "properties": { "script_hex": { "type": "string" }, "value_sat": { "type": "integer" }, "address": { "type": "string" } }
          } },
          "auth_message": { "type": ["string", "null"] },
          "warnings": { "type": "array", "items": { "type": "string" } }
        }
      }
    }
  }
}
