{
  "openapi": "3.1.0",
  "info": {
    "title": "Vaultion Escrow API",
    "version": "1.0.0",
    "summary": "Crypto escrow and direct crypto payments for any website, with one server-side call.",
    "description": "Create a hosted checkout where the buyer funds a USDC or USDT escrow held by a smart contract, or pays directly in crypto. No account and no API key: each request carries the seller's receiving addresses, and the checkout identity is derived from your site and those addresses.\n\nCall from your server only. Browser calls from other origins are refused.\n\nThere is no test mode: every network carries real payments. Escrow needs an order of at least $50. There are no webhooks: read the status with GET /v1/stores/sessions/{sessionId}. The buyer and the seller (store.email) are emailed at every escrow step automatically.\n\nEscrow disputes go to Vaultion-assisted human arbitration. It is not decentralized: you are trusting Vaultion's reviewers. A separate guardian can pause a ruling but never redirect funds.\n\nGuide: https://vaultion.org/developers",
    "contact": {
      "name": "Vaultion",
      "url": "https://vaultion.org/contact"
    },
    "termsOfService": "https://vaultion.org/payments/terms"
  },
  "externalDocs": {
    "description": "Developer guide",
    "url": "https://vaultion.org/developers"
  },
  "servers": [
    {
      "url": "https://checkout.vaultion.org"
    }
  ],
  "tags": [
    {
      "name": "Checkout",
      "description": "Create a checkout and read its status."
    },
    {
      "name": "Setup",
      "description": "Validate a seller's addresses and settings."
    }
  ],
  "paths": {
    "/v1/stores/sessions": {
      "post": {
        "tags": [
          "Checkout"
        ],
        "operationId": "createSession",
        "summary": "Create a checkout",
        "description": "Creates a hosted checkout and returns its path. Send the buyer to https://checkout.vaultion.org{checkoutPath}. A checkout stays open for 7 days. Repeating a request with the same orderRef returns the same checkout; the same orderRef with a different name or price is refused with order_ref_conflict.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SessionRequest"
              },
              "examples": {
                "escrowOnly": {
                  "summary": "Escrow only, $250",
                  "value": {
                    "store": {
                      "url": "https://yourplatform.com",
                      "name": "Your Platform",
                      "email": "seller@example.com"
                    },
                    "orderRef": "order-1042",
                    "name": "Logo design: final files",
                    "priceMinor": 25000,
                    "escrow": "required",
                    "receiving": {
                      "evm": "0x000000000000000000000000000000000000dEaD"
                    },
                    "returnUrl": "https://yourplatform.com/orders/1042"
                  }
                },
                "buyerChoice": {
                  "summary": "Buyer picks direct payment or escrow",
                  "value": {
                    "store": {
                      "url": "https://yourplatform.com",
                      "name": "Your Platform"
                    },
                    "orderRef": "order-1043",
                    "name": "Annual plan",
                    "priceMinor": 12000,
                    "escrow": "choice",
                    "feePayer": "buyer",
                    "receiving": {
                      "evm": "0x000000000000000000000000000000000000dEaD",
                      "bitcoin": "bc1q…",
                      "solana": "…"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Checkout created (or the existing checkout for this orderRef).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Refused"
          },
          "409": {
            "$ref": "#/components/responses/Refused"
          },
          "413": {
            "$ref": "#/components/responses/Refused"
          },
          "415": {
            "$ref": "#/components/responses/Refused"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/stores/sessions/{sessionId}": {
      "get": {
        "tags": [
          "Checkout"
        ],
        "operationId": "getSession",
        "summary": "Read a checkout's status",
        "description": "The order's current status, read from the chain. No key is needed: the session ID is unguessable. Poll every 30 to 60 seconds while the buyer is on the checkout, then every few minutes until the escrow resolves.",
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionStatus"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Refused"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/stores/check": {
      "post": {
        "tags": [
          "Setup"
        ],
        "operationId": "checkStore",
        "summary": "Validate addresses and settings",
        "description": "Validates a seller's receiving addresses and settings without creating anything, and lists the networks a checkout would offer. A refused address names its field, e.g. invalid_evm_address.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Valid. The networks a checkout would offer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckResult"
                },
                "example": {
                  "merchantId": "store_2555a5ed22a2a61cbd47259bf55c2307",
                  "networks": [
                    "base",
                    "ethereum",
                    "polygon",
                    "arbitrum",
                    "bsc"
                  ],
                  "escrowNetworks": [
                    "base",
                    "ethereum",
                    "arbitrum",
                    "bsc",
                    "tron",
                    "solana"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Refused"
          },
          "409": {
            "$ref": "#/components/responses/Refused"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Store": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "url",
          "name"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "Your site's URL. It identifies you; returnUrl must be on the same origin."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "The name the buyer sees on the checkout."
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254,
            "description": "The seller's email. The seller is emailed at every escrow step (funded, dispute, ruling, complete); the buyer adds their own email when funding. Your system is not notified: read the status."
          }
        }
      },
      "Receiving": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "description": "The seller's receiving addresses. Send at least one. Escrow needs an address on an escrow network: evm, tron or solana.",
        "properties": {
          "evm": {
            "type": "string",
            "description": "One address for Base, Ethereum, Arbitrum, Polygon and BNB Smart Chain. Escrow seller on Base, Arbitrum, Ethereum and BNB Smart Chain (USDC)."
          },
          "solana": {
            "type": "string",
            "description": "Solana address. Escrow in USDC or USDT."
          },
          "tron": {
            "type": "string",
            "description": "TRON address. Escrow in USDT."
          },
          "bitcoin": {
            "type": "string",
            "description": "Bitcoin address. Direct payments only."
          },
          "litecoin": {
            "type": "string",
            "description": "Litecoin address. Direct payments only."
          }
        }
      },
      "EscrowMode": {
        "type": "string",
        "enum": [
          "required",
          "choice",
          "off"
        ],
        "description": "required: escrow only. choice: the buyer picks direct payment or escrow (orders under $50 offer direct only). off: direct payments only."
      },
      "Network": {
        "type": "string",
        "enum": [
          "base",
          "ethereum",
          "arbitrum",
          "polygon",
          "bsc",
          "solana",
          "tron",
          "bitcoin",
          "litecoin"
        ]
      },
      "Brand": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "logo": {
            "type": "string",
            "maxLength": 12000,
            "pattern": "^data:image/(png|jpeg|webp);base64,",
            "description": "A PNG, JPEG or WebP data URL."
          },
          "accent": {
            "type": "string",
            "enum": [
              "navy",
              "blue",
              "gold",
              "white"
            ]
          }
        }
      },
      "Settings": {
        "type": "object",
        "properties": {
          "store": {
            "$ref": "#/components/schemas/Store"
          },
          "receiving": {
            "$ref": "#/components/schemas/Receiving"
          },
          "escrow": {
            "$ref": "#/components/schemas/EscrowMode"
          },
          "feePayer": {
            "type": "string",
            "enum": [
              "merchant",
              "buyer"
            ],
            "default": "merchant",
            "description": "Who pays the 0.75% direct-payment fee. The escrow fee is always paid by the buyer."
          },
          "theme": {
            "type": "string",
            "enum": [
              "auto",
              "light",
              "dark"
            ],
            "default": "auto"
          },
          "networks": {
            "type": "array",
            "minItems": 1,
            "maxItems": 12,
            "uniqueItems": true,
            "items": {
              "$ref": "#/components/schemas/Network"
            },
            "description": "Only offer these networks."
          },
          "brand": {
            "$ref": "#/components/schemas/Brand"
          }
        }
      },
      "CheckRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Settings"
          }
        ],
        "required": [
          "store",
          "receiving",
          "escrow"
        ],
        "unevaluatedProperties": false
      },
      "SessionRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Settings"
          }
        ],
        "required": [
          "store",
          "orderRef",
          "name",
          "priceMinor",
          "receiving",
          "escrow"
        ],
        "properties": {
          "orderRef": {
            "type": "string",
            "pattern": "^[a-zA-Z0-9_-]{1,80}$",
            "description": "Your order ID. The same orderRef returns the same checkout."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "What is being bought, as the buyer sees it."
          },
          "priceMinor": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100000000,
            "description": "The price in US cents. Escrow needs at least 5000 ($50)."
          },
          "returnUrl": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "Where \"Return to <store>\" goes once paid. Must be on store.url's origin."
          }
        },
        "unevaluatedProperties": false
      },
      "SessionCreated": {
        "type": "object",
        "required": [
          "merchantId",
          "sessionId",
          "checkoutPath"
        ],
        "properties": {
          "merchantId": {
            "type": "string",
            "description": "Your derived store identity, store_…"
          },
          "sessionId": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "checkoutPath": {
            "type": "string",
            "description": "/c/{sessionId}. Prefix with https://checkout.vaultion.org."
          }
        }
      },
      "Status": {
        "type": "string",
        "enum": [
          "unpaid",
          "paid",
          "reverted",
          "escrow_funded",
          "escrow_disputed",
          "escrow_pending",
          "escrow_released",
          "escrow_refunded",
          "escrow_closed",
          "escrow_late"
        ],
        "description": "unpaid: no confirmed payment yet. paid: a direct payment confirmed. reverted: reversed by a network reorganization, hold the order. escrow_funded: deliver, the seller is not paid yet. escrow_disputed: a dispute is open. escrow_pending: a ruling's challenge window is running, funds have not moved. escrow_released: the seller was paid or can claim it. escrow_refunded: the buyer was refunded or can claim it. escrow_closed: split between buyer and seller. escrow_late: funded after the checkout's price deadline, review before delivering."
      },
      "SessionStatus": {
        "type": "object",
        "required": [
          "sessionId",
          "status"
        ],
        "properties": {
          "sessionId": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/Status"
          },
          "network": {
            "type": "string",
            "description": "The network the order was paid on, e.g. base."
          },
          "label": {
            "type": "string",
            "description": "That network's display name."
          },
          "explorer": {
            "type": "string",
            "format": "uri",
            "description": "Block explorer for that network."
          },
          "settlement": {
            "type": [
              "object",
              "null"
            ],
            "description": "The confirmed payment, or null before one exists.",
            "properties": {
              "status": {
                "type": "string"
              },
              "attemptId": {
                "type": "string"
              },
              "txHash": {
                "type": "string",
                "description": "The buyer's payment or escrow-creation transaction."
              },
              "escrowId": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "escrowContract": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "received": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "BTC/LTC only: the amount received, in base units."
              },
              "merchantAmount": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "BTC/LTC only: the amount paid to the seller, in base units."
              },
              "payoutTxHash": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "BTC/LTC only: the transaction paying the seller."
              }
            }
          },
          "attempts": {
            "type": "array",
            "description": "Every payment attempt on this checkout, newest first.",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "CheckResult": {
        "type": "object",
        "required": [
          "merchantId",
          "networks",
          "escrowNetworks"
        ],
        "properties": {
          "merchantId": {
            "type": "string"
          },
          "networks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Network"
            },
            "description": "Networks a checkout would offer for direct payment."
          },
          "escrowNetworks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Network"
            },
            "description": "Networks Vaultion carries escrow on."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "escrow_below_minimum, base_escrow_not_enabled, invalid_<field>_address, receiving_address_required, order_ref_conflict, no_payment_options, invalid_store, invalid_order_ref, invalid_price, invalid_mode, invalid_return_url, invalid_brand, invalid_networks, invalid_theme, invalid_fee_payer, json_required, body_too_large, not_found, too_many_requests."
          }
        }
      }
    },
    "responses": {
      "Refused": {
        "description": "Refused. The error code says why.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "escrow_below_minimum"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Over 60 writes or 300 reads a minute from one IP. Retry after 60 seconds.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "too_many_requests"
            }
          }
        }
      }
    }
  }
}
