{
  "openapi": "3.1.0",
  "info": {
    "title": "Trufill Hosted Payments API",
    "version": "1.0.0",
    "description": "Create a USD payment server-side and redirect its buyer to hosted network selection. Fulfill orders only after a signed payment.succeeded webhook and authenticated payment readback. Public demo payments are sandbox simulations."
  },
  "servers": [
    {
      "url": "https://payments.trufill.xyz"
    }
  ],
  "paths": {
    "/v1/payments": {
      "post": {
        "operationId": "createPayment",
        "summary": "Create a merchant payment",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9:_-]{16,128}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePayment"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid merchant credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict or unavailable payment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request rate exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service or independent provider unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/payments/{paymentId}": {
      "get": {
        "operationId": "getPayment",
        "summary": "Authenticated authoritative merchant readback",
        "security": [
          {
            "merchantKey": []
          }
        ],
        "parameters": [
          {
            "name": "paymentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^pay_[a-f0-9]{64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "401": {
            "description": "Invalid merchant credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Payment not found in merchant account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/checkout/{paymentId}": {
      "get": {
        "operationId": "getCheckout",
        "summary": "Public checkout capability and network quotes",
        "parameters": [
          {
            "name": "paymentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^pay_[a-f0-9]{64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkout"
                }
              }
            }
          },
          "404": {
            "description": "Payment not found in merchant account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/checkout/{paymentId}/select": {
      "post": {
        "operationId": "selectNetwork",
        "summary": "Select a network from the same-origin checkout",
        "parameters": [
          {
            "name": "paymentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^pay_[a-f0-9]{64}$"
            }
          },
          {
            "name": "Origin",
            "in": "header",
            "required": true,
            "schema": {
              "const": "https://payments.trufill.xyz"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "network"
                ],
                "properties": {
                  "network": {
                    "$ref": "#/components/schemas/NetworkId"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Persisted selection and canonical child checkout URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "payment": {
                      "$ref": "#/components/schemas/Payment"
                    },
                    "checkout_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "network": {
                      "$ref": "#/components/schemas/NetworkId"
                    },
                    "quote": {
                      "$ref": "#/components/schemas/Quote"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Origin or sandbox policy rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict or unavailable payment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service or independent provider unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/demo/payments": {
      "post": {
        "operationId": "createDemoPayment",
        "summary": "Create an isolated anonymous sandbox payment",
        "description": "Same-origin only, 20 requests per address per minute, 8192-byte JSON body. Uses a fixed demo merchant, internal signed webhook receiver and fixed /demo/complete return.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "amount": {
                    "type": "string",
                    "default": "10.00",
                    "description": "10.00 through 1000.00 USD"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "403": {
            "description": "Origin or sandbox policy rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request rate exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/checkout/{paymentId}/simulate": {
      "post": {
        "operationId": "simulateSandboxPayment",
        "summary": "Confirm a selected sandbox payment",
        "description": "Same-origin only. Live merchant payments reject simulation. Body must be {}.",
        "parameters": [
          {
            "name": "paymentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^pay_[a-f0-9]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "403": {
            "description": "Origin or sandbox policy rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict or unavailable payment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "merchantKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Server-only merchant key issued by Trufill. Never expose it in browser code."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "Amount": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "value",
          "currency"
        ],
        "properties": {
          "value": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,6})\\.\\d{2}$",
            "example": "10.00"
          },
          "currency": {
            "const": "USD"
          }
        }
      },
      "CreatePayment": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "amount",
          "merchant_order_id",
          "return_url"
        ],
        "properties": {
          "amount": {
            "$ref": "#/components/schemas/Amount"
          },
          "merchant_order_id": {
            "type": "string",
            "pattern": "^[A-Za-z0-9._:-]{1,128}$",
            "example": "order-123"
          },
          "description": {
            "type": "string",
            "maxLength": 280,
            "example": "Hosting"
          },
          "return_url": {
            "type": "string",
            "format": "uri",
            "description": "HTTPS URL with an operator-registered merchant origin. The browser return conveys no proof of payment.",
            "example": "https://shop.example/return"
          }
        }
      },
      "Payment": {
        "type": "object",
        "required": [
          "id",
          "status",
          "amount",
          "merchant_order_id",
          "description",
          "created_at",
          "expires_at",
          "confirmation",
          "test"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^pay_[a-f0-9]{64}$"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "succeeded",
              "expired",
              "review"
            ]
          },
          "amount": {
            "$ref": "#/components/schemas/Amount"
          },
          "merchant_order_id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "test": {
            "type": "boolean"
          },
          "confirmation": {
            "type": "object",
            "required": [
              "type",
              "confirmation_url"
            ],
            "properties": {
              "type": {
                "const": "redirect"
              },
              "confirmation_url": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "selected_network": {
            "$ref": "#/components/schemas/NetworkId"
          },
          "return_url": {
            "type": "string",
            "format": "uri",
            "description": "Present after success. Registered return URL with payment_id appended. Informational browser navigation."
          },
          "settlement": {
            "type": "object",
            "properties": {
              "network": {
                "$ref": "#/components/schemas/NetworkId"
              },
              "asset": {
                "enum": [
                  "USDC",
                  "USDT"
                ]
              },
              "merchant_net": {
                "type": "string"
              },
              "processing_fee": {
                "type": "string"
              },
              "network_fee": {
                "type": "string"
              },
              "transaction_hash": {
                "type": "string"
              },
              "payout_transaction_hash": {
                "type": "string"
              },
              "simulated": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "NetworkId": {
        "type": "string",
        "enum": [
          "base",
          "arbitrum",
          "ethereum",
          "tron"
        ]
      },
      "Quote": {
        "type": "object",
        "required": [
          "invoice_amount",
          "processing_fee",
          "network_fee",
          "payer_amount",
          "merchant_net",
          "currency",
          "quoted_at",
          "expires_at",
          "illustrative"
        ],
        "properties": {
          "invoice_amount": {
            "type": "string",
            "description": "Six-decimal stablecoin units displayed with at least two decimal places."
          },
          "processing_fee": {
            "type": "string",
            "description": "Six-decimal stablecoin units displayed with at least two decimal places."
          },
          "network_fee": {
            "type": "string",
            "description": "Six-decimal stablecoin units displayed with at least two decimal places."
          },
          "payer_amount": {
            "type": "string",
            "description": "Six-decimal stablecoin units displayed with at least two decimal places."
          },
          "merchant_net": {
            "type": "string",
            "description": "Six-decimal stablecoin units displayed with at least two decimal places."
          },
          "currency": {
            "const": "USD"
          },
          "quoted_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "illustrative": {
            "type": "boolean",
            "description": "True for sandbox prices. Wallet sending network fee is separate. Unique child matching amount appears in the selected child checkout."
          }
        }
      },
      "Checkout": {
        "type": "object",
        "required": [
          "payment",
          "merchant",
          "networks",
          "test"
        ],
        "properties": {
          "payment": {
            "$ref": "#/components/schemas/Payment"
          },
          "merchant": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              }
            }
          },
          "networks": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "asset",
                "status"
              ],
              "properties": {
                "id": {
                  "$ref": "#/components/schemas/NetworkId"
                },
                "name": {
                  "type": "string"
                },
                "asset": {
                  "enum": [
                    "USDC",
                    "USDT"
                  ]
                },
                "status": {
                  "enum": [
                    "available",
                    "unavailable"
                  ]
                },
                "reason": {
                  "type": "string"
                },
                "quote": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "test": {
            "type": "boolean"
          },
          "selected_network": {
            "$ref": "#/components/schemas/NetworkId"
          },
          "checkout_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "PaymentSucceeded": {
        "type": "object",
        "required": [
          "id",
          "event",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable payment.succeeded:<payment id> deduplication key."
          },
          "event": {
            "const": "payment.succeeded"
          },
          "data": {
            "$ref": "#/components/schemas/Payment"
          }
        }
      }
    }
  },
  "webhooks": {
    "payment.succeeded": {
      "post": {
        "summary": "Signed fulfillment event delivered to the registered merchant URL",
        "description": "Webhook destination is configured by the operator. x-trufill-signature is t=<unix seconds>,v1=<HMAC-SHA256(secret,timestamp + \".\" + id + \".\" + raw body)>. x-trufill-idempotency-key is the stable event id. Verify timestamp tolerance and signature against raw bytes, deduplicate the event id, then independently GET /v1/payments/{id} with merchant credentials and compare amount, merchant_order_id and succeeded status before fulfillment. Return 2xx after durable acceptance. Delivery uses a durable outbox with up to 17 attempts and capped exponential retry.",
        "parameters": [
          {
            "name": "x-trufill-signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "x-trufill-idempotency-key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentSucceeded"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Merchant durably accepted event"
          }
        }
      }
    }
  }
}
