{
  "openapi": "3.0.4",
  "info": {
    "title": "Nagora Agentic Commerce API",
    "description": "API for AI agents to transact end-to-end on Nagora. Agents bring their own Nano wallet; Nagora provides the exchange layer.",
    "contact": {
      "email": "hello@nagora.shop"
    },
    "version": "agents-v1"
  },
  "paths": {
    "/agents/v1/register": {
      "post": {
        "tags": [
          "nagora-api"
        ],
        "summary": "Self-register an agent (no account required)",
        "description": "Creates an agent principal and mints its first API key in a single call — no Nagora account needed.\n\nRequired: `name` and a valid `nanoAddress` (your wallet; refunds are routed here if an order is cancelled or refunded). Optional: `contactEmail`, `homepageUrl`, `callbackUrl` for webhooks (a `webhookSecret` is generated and returned when set).\n\nThe `plaintextKey` in the response is shown **once only** — persist it immediately. Use it as `Authorization: Bearer nag_agt_...` on all agent endpoints.\n\nSelf-registered keys get conservative default spending caps (`maxXnoPerTx`, `maxXnoPerDay`, returned in the response). To raise caps, manage keys, or attach the agent to a human account, create a Nagora account and register the agent from `/settings/agents` instead.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SelfRegisterAgentRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/agents/v1/me": {
      "get": {
        "tags": [
          "nagora-api"
        ],
        "summary": "Introspect authenticated agent",
        "description": "Returns the authenticated agent's identity, spending caps for the current API key, `callbackUrl`, and `webhookSecret`.\n\nUse this on startup to confirm the key is valid and to retrieve the `webhookSecret` needed to verify incoming webhook signatures. Verify a webhook by computing `HMAC-SHA256(\"{X-Nagora-Timestamp}.{raw_body}\", webhookSecret)` and comparing (constant-time) to `X-Nagora-Signature: sha256=<hex>`.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/agents/v1/listings": {
      "get": {
        "tags": [
          "nagora-api"
        ],
        "summary": "Search listings",
        "description": "Anonymous full-text search over active listings. No API key required. Use `q`, `categoryId`, `minPrice`, `maxPrice`, and `sortBy` (newest|price_asc|price_desc) to filter results. Returns `items` array and `total` count.",
        "parameters": [
          {
            "name": "Q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CategoryId",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "MinPrice",
            "in": "query",
            "schema": {
              "type": "number",
              "format": "double"
            }
          },
          {
            "name": "MaxPrice",
            "in": "query",
            "schema": {
              "type": "number",
              "format": "double"
            }
          },
          {
            "name": "SortBy",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "StartIndex",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "Count",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/agents/v1/listings/{id}": {
      "get": {
        "tags": [
          "nagora-api"
        ],
        "summary": "Get listing detail",
        "description": "Fetch a single listing by ID. Anonymous — no API key required. Returns full listing including variants, delivery options, and pricing details needed to construct a purchase request.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/agents/v1/purchases": {
      "post": {
        "tags": [
          "nagora-api"
        ],
        "summary": "Create a purchase",
        "description": "Place an order against a listing. Returns a `depositAddress` (on-chain Nano address) and `amountNano` — the agent must send exactly that amount to the deposit address to fund escrow.\n\nSupply `quotedTotalNano` from a recent listing fetch to guard against FX drift (rejected if >2% off). Omit it to skip the check.\n\n**State after this call:** `OrderStatus=Pending`, `EscrowStatus=Initiated`. Once on-chain funds are detected the escrow automatically moves to `Funded` / `AwaitingShipment` and a `order.funded` webhook fires (if configured). When the seller ships, `order.shipped` fires and the agent should call `POST /agents/v1/orders/{orderId}/confirm` to release funds to the seller.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentPurchaseRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/agents/v1/orders/{orderId}": {
      "get": {
        "tags": [
          "nagora-api"
        ],
        "summary": "Poll order and escrow status",
        "description": "Returns the current `orderStatus`, `escrowStatus`, funding timestamps, tracking info, and receipt ID. Poll this after funding the deposit address or after receiving a webhook event to verify the transition has completed before acting.",
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/agents/v1/orders/{orderId}/receipt": {
      "get": {
        "tags": [
          "nagora-api"
        ],
        "summary": "Get signed receipt",
        "description": "Returns the KMS-signed receipt after escrow is released. The receipt includes `sellerNanoAddress`, `escrowDepositAddress`, `sellerPayoutBlock` (on-chain hash), `nagora_signature`, and optionally the seller's own signature. Available only after `EscrowStatus=Released`. Returns 404 while the order is still in progress.",
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/agents/v1/orders/{orderId}/confirm": {
      "post": {
        "tags": [
          "nagora-api"
        ],
        "summary": "Confirm delivery and release funds",
        "description": "Signals that the agent has received the goods or digital content. Transitions escrow `Shipped → Delivered → Released` and triggers the on-chain payout to the seller.\n\n**Call this promptly after receiving an `order.shipped` webhook** — the seller is waiting for this to get paid. If not called, the auto-finalise timer releases funds automatically after the configured window, but this adds unnecessary delay.\n\nRequires `EscrowStatus=Shipped`. Returns the released escrow DTO.",
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/agents/v1/orders/{orderId}/cancel": {
      "post": {
        "tags": [
          "nagora-api"
        ],
        "summary": "Cancel an unfunded order",
        "description": "Cancels an order whose escrow is still `Initiated` (no on-chain funds received yet). Restores listing stock and marks escrow `Cancelled`. Returns 204 on success. Cannot cancel once `EscrowStatus=Funded` — use the dispute or refund flow instead.",
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/agents/v1/listings/{id}/content": {
      "get": {
        "tags": [
          "nagora-api"
        ],
        "summary": "Buy a digital listing over x402",
        "description": "x402 v2 `exact` scheme on `nano:mainnet` (https://github.com/x402nano/schemes/blob/main/exact.md), for listings with `deliveryOptions.autoDeliver=true`.\n\nCall without a `PAYMENT-SIGNATURE` header: places an unpaid order and returns 402 with the offer in the `PAYMENT-REQUIRED` header (base64 JSON) and the body. `payTo` is this order's own deposit address and `amount` is in raw.\n\nRetry with `PAYMENT-SIGNATURE` carrying a signed, unpublished send block for exactly `amount` to `payTo`. Nagora publishes it, waits for confirmation, funds escrow and returns 200 with `content`, plus a `PAYMENT-RESPONSE` header holding the block hash. Escrow then releases to the seller on the usual auto-finalise timer, or sooner via `POST /agents/v1/orders/{orderId}/confirm`.\n\nSpending caps and the pending-order cap apply as for `POST /agents/v1/purchases`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AgentPurchaseRequest": {
        "type": "object",
        "properties": {
          "listingId": {
            "type": "string",
            "format": "uuid"
          },
          "quantity": {
            "type": "integer",
            "format": "int32"
          },
          "deliveryOptionId": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "variantId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "shippingAddress": {
            "$ref": "#/components/schemas/AgentShippingAddress"
          },
          "quotedTotalNano": {
            "type": "number",
            "format": "double",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "AgentShippingAddress": {
        "type": "object",
        "properties": {
          "fullName": {
            "type": "string",
            "nullable": true
          },
          "line1": {
            "type": "string",
            "nullable": true
          },
          "line2": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "state": {
            "type": "string",
            "nullable": true
          },
          "postalCode": {
            "type": "string",
            "nullable": true
          },
          "countryCode": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "SelfRegisterAgentRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true
          },
          "nanoAddress": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "homepageUrl": {
            "type": "string",
            "nullable": true
          },
          "contactEmail": {
            "type": "string",
            "nullable": true
          },
          "callbackUrl": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      }
    },
    "securitySchemes": {
      "Bearer": {
        "type": "apiKey",
        "description": "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
        "name": "Authorization",
        "in": "header"
      },
      "AgentApiKey": {
        "type": "apiKey",
        "description": "Agent API key. Format: `Bearer nag_agt_<8-char-prefix>_<32-char-secret>`. Obtain via POST /agents/{id}/keys (requires a logged-in account).",
        "name": "Authorization",
        "in": "header"
      }
    }
  },
  "security": [
    {
      "AgentApiKey": [ ]
    }
  ],
  "tags": [
    {
      "name": "nagora-api"
    }
  ]
}