{
  "openapi": "3.1.0",
  "info": {
    "title": "agentpay: SOL pay links for AI agents",
    "version": "1.0.0",
    "description": "Non-custodial SOL invoices and payment verification for AI agents. Create an invoice (Solana Pay transfer request with a fresh reference key), give the payer the url or QR, then poll status until paid:true. SOL goes directly from payer to `to`. No auth, no fee. See https://agentpay.wtf/llms.txt"
  },
  "servers": [
    {
      "url": "https://agentpay.wtf"
    }
  ],
  "paths": {
    "/api/invoice": {
      "post": {
        "operationId": "createInvoice",
        "summary": "Create a single-use SOL invoice",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceRequest"
              },
              "example": {
                "to": "<AGENT_WALLET>",
                "amount": "0.05",
                "label": "research-bot-7",
                "memo": "order-1234"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invoice created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "createInvoiceGet",
        "summary": "Create an invoice using query parameters (same as POST)",
        "parameters": [
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "amount",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "label",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "message",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "memo",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cluster",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Invoice created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoice/status": {
      "get": {
        "operationId": "getInvoiceStatus",
        "summary": "Check on-chain whether an invoice has been paid",
        "description": "getSignaturesForAddress(reference) at confirmed commitment, then validates each tx (oldest first): success, includes reference, SystemProgram transfer of >= amount to `to`, memo matches if given. Uses @solana/pay validateTransfer, with a lenient fallback for versioned transactions.",
        "parameters": [
          {
            "name": "reference",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "reference public key from createInvoice"
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "recipient wallet"
          },
          {
            "name": "amount",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "expected SOL amount; leave out to accept any amount > 0"
          },
          {
            "name": "memo",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "require this exact memo"
          },
          {
            "name": "cluster",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "mainnet-beta",
                "devnet"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "RPC error, retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/x/claim/start": {
      "post": {
        "operationId": "xClaimStart",
        "summary": "Start claiming an X handle: get the message to sign and the code to post",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "handle",
                  "wallet"
                ],
                "additionalProperties": false,
                "properties": {
                  "handle": {
                    "type": "string",
                    "description": "X handle, with or without @"
                  },
                  "wallet": {
                    "type": "string",
                    "description": "Solana wallet (base58)"
                  }
                }
              },
              "example": {
                "handle": "research_bot",
                "wallet": "<AGENT_WALLET>"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Challenge",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/XChallenge"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/api/x/claim/finish": {
      "post": {
        "operationId": "xClaimFinish",
        "summary": "Finish a claim: verify the wallet signature and the X post, then save handle -> wallet",
        "description": "Saves only when BOTH checks pass: (a) ed25519 signature of the exact challenge message by `wallet` (tweetnacl), (b) a public post by `handle` containing the code, newer than the claim, read keylessly from X (syndication, then oEmbed).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "handle",
                  "wallet",
                  "signature",
                  "tweetUrl"
                ],
                "additionalProperties": false,
                "properties": {
                  "handle": {
                    "type": "string"
                  },
                  "wallet": {
                    "type": "string"
                  },
                  "signature": {
                    "type": "string",
                    "description": "64-byte ed25519 signature of `message`, base58 (base64/hex accepted)"
                  },
                  "tweetUrl": {
                    "type": "string",
                    "description": "https://x.com/<handle>/status/<id>"
                  },
                  "bio": {
                    "type": "string",
                    "maxLength": 140
                  },
                  "suggestedAmount": {
                    "type": "string",
                    "description": "SOL, preselected in the tip jar"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verified and saved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/XHandle"
                }
              }
            }
          },
          "400": {
            "description": "Not verified: reason is wrong_author, code_missing, stale_tweet, tweet_not_found, reused_tweet, or field=signature",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited"
          },
          "502": {
            "description": "X could not be read (reason x_unreachable); nothing saved"
          }
        }
      }
    },
    "/api/x/{handle}": {
      "get": {
        "operationId": "xGetHandle",
        "summary": "Look up an X handle's verified wallet and pay links",
        "parameters": [
          {
            "name": "handle",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Verified handle",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/XHandle"
                }
              }
            }
          },
          "404": {
            "description": "Not claimed: {verified:false, claimUrl}"
          }
        }
      }
    },
    "/api/x/{handle}/invoice": {
      "post": {
        "operationId": "xCreateInvoice",
        "summary": "Create a single-use invoice paying an X handle's verified wallet",
        "parameters": [
          {
            "name": "handle",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "amount": {
                    "type": "string"
                  },
                  "memo": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "message": {
                    "type": "string",
                    "maxLength": 140
                  }
                }
              },
              "example": {
                "amount": "0.05",
                "memo": "job-12"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invoice (url is the handle page)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          },
          "404": {
            "description": "Handle not claimed"
          }
        }
      }
    },
    "/api/x/mention": {
      "post": {
        "operationId": "xMention",
        "summary": "Turn a tagged X post into a reply (and an invoice) for bots that poll their mentions",
        "description": "Parses \"<@mentions> pay [@target] <amount> [SOL] [for <memo>]\" or \"<@mentions> tip [@target]\". Never posts to X.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "text"
                ],
                "additionalProperties": false,
                "properties": {
                  "text": {
                    "type": "string"
                  },
                  "author": {
                    "type": "string",
                    "description": "handle that posted"
                  },
                  "bot": {
                    "type": "string",
                    "description": "your bot handle; must be tagged up front"
                  }
                }
              },
              "example": {
                "text": "@UseTaggedBot pay @research_bot 0.05 for job-12",
                "author": "alice",
                "bot": "UseTaggedBot"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{action:\"reply\", kind, reply, invoice?} or {action:\"silent\", reason}"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "XChallenge": {
        "type": "object",
        "properties": {
          "handle": {
            "type": "string"
          },
          "wallet": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "example": "ap-7KQ2MX"
          },
          "message": {
            "type": "string",
            "description": "Exact text to sign"
          },
          "tweetText": {
            "type": "string"
          },
          "tweetIntentUrl": {
            "type": "string"
          },
          "issuedAt": {
            "type": "string"
          },
          "expiresAt": {
            "type": "string"
          }
        }
      },
      "XHandle": {
        "type": "object",
        "properties": {
          "handle": {
            "type": "string"
          },
          "verified": {
            "type": "boolean"
          },
          "wallet": {
            "type": "string"
          },
          "payUrl": {
            "type": "string"
          },
          "solanaPayUrl": {
            "type": "string"
          },
          "invoiceUrl": {
            "type": "string"
          },
          "x": {
            "type": "string"
          },
          "displayName": {
            "type": [
              "string",
              "null"
            ]
          },
          "bio": {
            "type": [
              "string",
              "null"
            ]
          },
          "suggestedAmount": {
            "type": [
              "string",
              "null"
            ]
          },
          "verifiedAt": {
            "type": "string"
          },
          "tweetUrl": {
            "type": "string"
          }
        }
      },
      "InvoiceRequest": {
        "type": "object",
        "required": [
          "to"
        ],
        "additionalProperties": false,
        "properties": {
          "to": {
            "type": "string",
            "description": "Recipient Solana wallet (base58)"
          },
          "amount": {
            "type": "string",
            "description": "SOL amount as decimal string, e.g. \"0.05\". Optional (tip invoice). Max 10000, max 9 decimals."
          },
          "label": {
            "type": "string",
            "maxLength": 64,
            "description": "Agent or merchant name shown to the payer"
          },
          "message": {
            "type": "string",
            "maxLength": 140,
            "description": "Shown to the payer"
          },
          "memo": {
            "type": "string",
            "maxLength": 120,
            "description": "Added on-chain as an SPL Memo (public)"
          },
          "cluster": {
            "type": "string",
            "enum": [
              "mainnet-beta",
              "devnet"
            ],
            "default": "mainnet-beta"
          }
        }
      },
      "Invoice": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "Human pay page (Phantom button + QR + live status)"
          },
          "reference": {
            "type": "string",
            "description": "Fresh random public key identifying this payment"
          },
          "solanaPayUrl": {
            "type": "string",
            "description": "solana: transfer request URL"
          },
          "qr": {
            "type": "string",
            "description": "PNG data URL QR code of solanaPayUrl"
          },
          "statusUrl": {
            "type": "string",
            "description": "Ready-made status URL to poll"
          },
          "to": {
            "type": "string"
          },
          "amount": {
            "type": [
              "string",
              "null"
            ]
          },
          "label": {
            "type": [
              "string",
              "null"
            ]
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          },
          "memo": {
            "type": [
              "string",
              "null"
            ]
          },
          "cluster": {
            "type": "string"
          }
        }
      },
      "Status": {
        "type": "object",
        "properties": {
          "paid": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "confirming",
              "invalid",
              "paid"
            ]
          },
          "signature": {
            "type": [
              "string",
              "null"
            ]
          },
          "amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "SOL transferred to `to` in the tx"
          },
          "lamports": {
            "type": "string"
          },
          "payer": {
            "type": [
              "string",
              "null"
            ]
          },
          "slot": {
            "type": [
              "integer",
              "null"
            ]
          },
          "blockTime": {
            "type": [
              "integer",
              "null"
            ]
          },
          "confirmationStatus": {
            "type": [
              "string",
              "null"
            ]
          },
          "validator": {
            "type": "string",
            "description": "\"@solana/pay validateTransfer\" or \"lenient\""
          },
          "reason": {
            "type": "string",
            "description": "Why a found transaction did not validate"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "field": {
            "type": "string"
          }
        }
      }
    }
  }
}