{
  "openapi": "3.1.0",
  "info": {
    "title": "Partner Connect Hub Public API",
    "version": "1.0.0",
    "description": "OAuth2 client_credentials API for partner integrations. Obtain a token at `/oauth/token`, then send `Authorization: Bearer <token>`. Rate limit: configurable per client (default 120 req/min)."
  },
  "servers": [
    {
      "url": "https://sunrift-hub.com",
      "description": "Production"
    },
    {
      "url": "https://project--4c706f75-e5f6-4efe-b28a-d65902ccfbd9-dev.lovable.app",
      "description": "Preview"
    }
  ],
  "components": {
    "securitySchemes": {
      "OAuth2": {
        "type": "oauth2",
        "flows": {
          "clientCredentials": {
            "tokenUrl": "/oauth/token",
            "scopes": {
              "qr:create": "Generate QR codes / payment intents",
              "identity:read": "Read identity users",
              "identity:write": "Create / update identity users",
              "ledger:read": "Read accounting transactions",
              "webhooks:receive": "Receive HMAC-signed outbound webhooks",
              "partners:*": "All partner integrations",
              "payments:write": "Create Stripe checkout sessions via the Hub-managed Connect account",
              "payments:read": "Read status of previously created checkout sessions",
              "hub_wallets:read": "Read Hub wallet balances, holds, transactions, and spend-authorisation requests",
              "hub_wallets:topup": "Create a Stripe Checkout that credits a Hub identity wallet (v2 deterministic-net contract)",
              "hub_wallets:hold": "Place a hold on a Hub wallet against a spend-authorisation",
              "hub_wallets:capture": "Capture or release an existing hold",
              "hub_wallets:refund": "Refund a captured wallet charge",
              "hub_companies:read": "Read KYB status and wallet balances of a legal-entity identity linked to the project",
              "hub_companies:write": "Manage company-level resources (reserved for future operations)"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "example": "invalid_token"
          },
          "error_description": {
            "type": "string"
          }
        }
      },
      "Order": {
        "type": "object",
        "properties": {
          "order_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "completed",
              "failed"
            ]
          },
          "partner_order_id": {
            "type": "string",
            "nullable": true
          },
          "amount_retail": {
            "type": "number"
          },
          "currency_retail": {
            "type": "string",
            "minLength": 3,
            "maxLength": 3
          }
        }
      },
      "IdentityUser": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "display_name": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "PaymentStatus": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "succeeded",
              "failed",
              "canceled",
              "expired"
            ]
          },
          "amount_minor": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "client_reference_id": {
            "type": "string",
            "nullable": true
          },
          "identity_user_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "checkout_session_id": {
            "type": "string",
            "nullable": true
          },
          "payment_intent_id": {
            "type": "string",
            "nullable": true
          },
          "checkout_url": {
            "type": "string",
            "nullable": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "succeeded_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "failed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "canceled_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "failure": {
            "type": "object",
            "nullable": true,
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Invalid or expired token",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds until reset"
          },
          "X-RateLimit-Limit": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset": {
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "OAuth2": []
    }
  ],
  "paths": {
    "/api/v1/enroll": {
      "post": {
        "summary": "Self-enroll a new partner project (one-time token)",
        "description": "Exchange a one-time enrollment token (`Authorization: Bearer ent_...`) for a fully-provisioned project: OAuth client credentials and webhook signing secret are returned exactly once. Token is consumed on success.",
        "security": [],
        "parameters": [
          {
            "name": "Authorization",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "Bearer ent_xxx"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "webhook_url",
                  "requested_scopes"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 120
                  },
                  "slug": {
                    "type": "string",
                    "description": "lowercase letters/digits/_/-, optional"
                  },
                  "webhook_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "https only, public host"
                  },
                  "contact_email": {
                    "type": "string",
                    "format": "email"
                  },
                  "requested_scopes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "maxItems": 20
                  },
                  "payment_code": {
                    "type": "string",
                    "pattern": "^[A-Z]{2}$"
                  },
                  "payment_purpose_template": {
                    "type": "string",
                    "maxLength": 210,
                    "description": "must contain {{ID}}"
                  },
                  "environment": {
                    "type": "string",
                    "enum": [
                      "production",
                      "staging"
                    ],
                    "default": "production"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Project provisioned. client_secret and webhook.secret are returned ONCE.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "project": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "slug": {
                              "type": "string"
                            },
                            "name": {
                              "type": "string"
                            }
                          }
                        },
                        "oauth": {
                          "type": "object",
                          "properties": {
                            "client_id": {
                              "type": "string"
                            },
                            "client_secret": {
                              "type": "string"
                            },
                            "environment": {
                              "type": "string"
                            },
                            "scopes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "token_endpoint": {
                              "type": "string",
                              "format": "uri"
                            }
                          }
                        },
                        "webhook": {
                          "type": "object",
                          "properties": {
                            "url": {
                              "type": "string",
                              "format": "uri"
                            },
                            "signature_alg": {
                              "type": "string",
                              "example": "ed25519"
                            },
                            "signature_header": {
                              "type": "string",
                              "example": "X-Hub-Signature"
                            },
                            "signature_kid_header": {
                              "type": "string",
                              "example": "X-Hub-Signature-Kid"
                            },
                            "signature_timestamp_header": {
                              "type": "string",
                              "example": "X-Hub-Signature-Timestamp"
                            },
                            "signed_message_format": {
                              "type": "string",
                              "example": "${X-Hub-Signature-Timestamp}.${raw_body}"
                            },
                            "delivery_id_header": {
                              "type": "string",
                              "example": "X-Hub-Delivery"
                            },
                            "event_header": {
                              "type": "string",
                              "example": "X-Hub-Event"
                            },
                            "verification": {
                              "type": "object",
                              "properties": {
                                "jwks_url": {
                                  "type": "string",
                                  "format": "uri"
                                },
                                "notes": {
                                  "type": "string"
                                }
                              }
                            }
                          }
                        },
                        "defaults": {
                          "type": "object",
                          "properties": {
                            "bank_account_id": {
                              "type": "string",
                              "format": "uuid",
                              "nullable": true
                            },
                            "qr_template_id": {
                              "type": "string",
                              "format": "uuid",
                              "nullable": true
                            }
                          }
                        },
                        "next_step": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "invalid_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/enroll/confirm": {
      "post": {
        "summary": "Confirm enrollment (mark project active)",
        "description": "Authenticate with HTTP Basic (client_id:client_secret) to flip the enrollment ticket from `enrolled` to `active`.",
        "security": [],
        "parameters": [
          {
            "name": "Authorization",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "Basic base64(client_id:client_secret)"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Project activated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "status": {
                          "type": "string",
                          "example": "active"
                        },
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "summary": "Issue access token (client_credentials)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type",
                  "client_id",
                  "client_secret"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "client_credentials"
                    ]
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  },
                  "scope": {
                    "type": "string",
                    "description": "Space-separated scopes (optional)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "access_token": {
                      "type": "string"
                    },
                    "token_type": {
                      "type": "string",
                      "example": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer",
                      "example": 3600
                    },
                    "scope": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/payments/checkout-sessions": {
      "post": {
        "summary": "Create a Stripe Checkout Session via Hub-managed Connect account",
        "description": "Creates a Stripe Direct Charge checkout session on the Hub's Connect account for this project. Requires `Idempotency-Key` (8..200 chars; we recommend a stable UUID per business intent — NOT per HTTP retry). The returned `payment_record_id` is the canonical key — store it BEFORE redirecting the user; it joins your domain object to the outbound `payment.succeeded` / `payment.failed` webhook. `client_reference_id` is echoed back and is a secondary lookup key. **Full integration guide: `/developers/payments`.**",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount_minor",
                  "currency",
                  "success_url",
                  "cancel_url",
                  "product_name"
                ],
                "properties": {
                  "amount_minor": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 99999999,
                    "description": "Amount in minor units (e.g. 900 = $9.00)"
                  },
                  "currency": {
                    "type": "string",
                    "example": "usd",
                    "description": "Lowercase ISO 4217 code. See `/api/v1/openapi.json` for the supported set."
                  },
                  "success_url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000
                  },
                  "cancel_url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000
                  },
                  "product_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 250
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "client_reference_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "Your domain ID (deposit_request.id, order.id). Echoed back in webhooks."
                  },
                  "identity_user_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "customer_email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Session created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Hub payment_record_id"
                        },
                        "checkout_session_id": {
                          "type": "string",
                          "example": "cs_live_..."
                        },
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "insufficient_scope (requires payments:write)"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "description": "stripe_error — upstream Stripe failure"
          }
        }
      }
    },
    "/api/v1/payments/checkout-sessions/{id}": {
      "get": {
        "summary": "Get checkout session status",
        "description": "Compatibility endpoint. Prefer `/api/v1/payments/{id}` or `/api/v1/payments?checkout_session_id=...` for success-page polling. Accepts either Hub payment_record_id (UUID) or the Stripe `cs_...` ID. Scope: `payments:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "processing",
                            "succeeded",
                            "failed",
                            "canceled",
                            "expired"
                          ]
                        },
                        "amount_minor": {
                          "type": "integer"
                        },
                        "currency": {
                          "type": "string"
                        },
                        "client_reference_id": {
                          "type": "string",
                          "nullable": true
                        },
                        "identity_user_id": {
                          "type": "string",
                          "format": "uuid",
                          "nullable": true
                        },
                        "checkout_session_id": {
                          "type": "string",
                          "nullable": true
                        },
                        "payment_intent_id": {
                          "type": "string",
                          "nullable": true
                        },
                        "checkout_url": {
                          "type": "string",
                          "nullable": true
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        },
                        "succeeded_at": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        },
                        "failed_at": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        },
                        "canceled_at": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        },
                        "failure": {
                          "type": "object",
                          "nullable": true,
                          "properties": {
                            "code": {
                              "type": "string"
                            },
                            "message": {
                              "type": "string"
                            }
                          }
                        },
                        "metadata": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "not_found"
          }
        }
      }
    },
    "/api/v1/payments/{id}": {
      "get": {
        "summary": "Get payment status",
        "description": "Canonical payment status endpoint for success-page polling. Accepts Hub `payment_record_id` (UUID) or Stripe `cs_...` Checkout Session id. Scope: `payments:read`. Polling is a fallback, not the primary confirmation channel — webhooks (`payment.succeeded` / `payment.failed`) are. Never perform business writes (credit deposit, fulfill order) from the polling code path; do them only in your webhook handler, idempotent by `payment_record_id`. **Full integration guide: `/developers/payments`.**",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "$ref": "#/components/schemas/PaymentStatus"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_request"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "insufficient_scope (requires payments:read)"
          },
          "404": {
            "description": "not_found"
          }
        }
      }
    },
    "/api/v1/payments": {
      "get": {
        "summary": "Find payment status",
        "description": "Query-form status endpoint for success pages. Provide exactly one of `checkout_session_id`, `payment_id`, or `client_reference_id`. `checkout_session_id` normally contains Stripe `cs_...`; for backwards compatibility it also accepts a Hub payment UUID and resolves it as `payment_id`. Scope: `payments:read`. **Polling is a fallback, not the primary confirmation channel — use webhooks for credit/fulfillment writes.** Full integration guide: `/developers/payments`.",
        "parameters": [
          {
            "name": "checkout_session_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "example": "cs_live_..."
            }
          },
          {
            "name": "payment_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "client_reference_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "$ref": "#/components/schemas/PaymentStatus"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_request"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "insufficient_scope (requires payments:read)"
          },
          "404": {
            "description": "not_found"
          }
        }
      }
    },
    "/api/v1/hub-wallets/topup": {
      "post": {
        "summary": "Create a Stripe Checkout that credits a Hub identity wallet",
        "description": "**Deterministic-net (v2) contract.** Caller charges the user `amount_minor` (gross) but commits to credit the wallet with exactly `expected_net_minor` on success. The fee buffer (`gross − expected_net`) absorbs Stripe processing fees plus an optional Hub margin; the actual Stripe fee variance NEVER reaches the user. Caller validates math: `amount_minor == ceil((expected_net_minor + fee_fixed_minor) / (1 − fee_rate_bps/10000))` ±1 minor unit. All three v2 fields (`expected_net_minor`, `fee_rate_bps`, `fee_fixed_minor`) must be present together or all absent (legacy mode credits gross). Recommended preset: `fee_rate_bps=400`, `fee_fixed_minor=30`. On success Hub posts `payment.succeeded` with `gross_amount_minor` and (when Stripe BalanceTransaction is ready) `net_amount_minor`+`processing_fee_minor`. **The partner must credit the user wallet with `expected_net_minor` (mirrored to webhook payload metadata as `hub_expected_net_minor`) — NOT `net_amount_minor`. The actual Stripe net is informational for Hub PnL only.** Hub's own ledger posts the three-leg entry `+expected_net → user_wallet`, `−gross → gateway:stripe`, `+(gross−expected_net) → revenue:deposit_margin`. Full spec: `docs/deposit-fee-spec.md`. Tenant guard: `identity_user_id` must already be linked to the calling project. Scope: `hub_wallets:topup`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "identity_user_id",
                  "amount_minor",
                  "currency",
                  "success_url",
                  "cancel_url"
                ],
                "properties": {
                  "identity_user_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "amount_minor": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 99999999,
                    "description": "Gross amount charged to the user, minor units"
                  },
                  "currency": {
                    "type": "string",
                    "example": "usd"
                  },
                  "success_url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000
                  },
                  "cancel_url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000
                  },
                  "customer_email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "product_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 250
                  },
                  "idempotency_key": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 128
                  },
                  "expected_net_minor": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 99999999,
                    "description": "v2: net amount the partner promises to credit to the user wallet. MUST be < amount_minor."
                  },
                  "fee_rate_bps": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 2000,
                    "description": "v2: percentage component of the gross-up, in basis points (0..20%)."
                  },
                  "fee_fixed_minor": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10000,
                    "description": "v2: fixed component of the gross-up, in minor units."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Checkout session created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "payment_record_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "checkout_session_id": {
                          "type": "string",
                          "example": "cs_live_..."
                        },
                        "checkout_url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_request (missing/inconsistent v2 fields)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "insufficient_scope (requires hub_wallets:topup) or identity_not_linked"
          },
          "422": {
            "description": "invalid_deposit_math — amount_minor does not match the v2 gross-up formula within ±1 minor unit"
          },
          "502": {
            "description": "stripe_error"
          }
        }
      }
    },
    "/api/v1/hub-wallets/balances": {
      "get": {
        "summary": "Read Hub wallet balances for a linked identity",
        "description": "Returns aggregated balances per currency for the given identity. Scope: `hub_wallets:read`. The identity must be linked to the calling project.",
        "parameters": [
          {
            "name": "identity_user_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "insufficient_scope or identity_not_linked"
          }
        }
      }
    },
    "/api/v1/hub-companies/{identity_user_id}": {
      "get": {
        "summary": "Read KYB status and balances of a company identity",
        "description": "Returns the legal-entity profile (jurisdiction, tax_id, kyb_status, activated_at) together with per-currency balances. The identity MUST be linked to the calling project via `identity_project_links` — otherwise 404. KYB activation happens automatically after the first inbound bank transfer with a matching payer INN. Scope: `hub_companies:read`.",
        "parameters": [
          {
            "name": "identity_user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "insufficient_scope"
          },
          "404": {
            "description": "not_found (not a company or not linked to project)"
          }
        }
      }
    },
    "/api/v1/hub-wallets/holds": {
      "post": {
        "summary": "Place a hold against a spend-authorisation",
        "description": "Reserves `amount_minor` from the wallet under an active spend-authorisation. Idempotent on `idempotency_key`. Scope: `hub_wallets:hold`. Released holds expire automatically via the `wallet-holds-expire` cron.",
        "responses": {
          "201": {
            "description": "Hold placed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "insufficient_scope"
          },
          "409": {
            "description": "insufficient_funds or authorisation_not_active"
          }
        }
      }
    },
    "/api/v1/hub-wallets/holds/{id}/capture": {
      "post": {
        "summary": "Capture a previously placed hold",
        "description": "Converts a hold into a settled debit (`-amount → user_wallet`, `+amount → project:revenue`). Idempotent on the hold id. Scope: `hub_wallets:capture`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Captured"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "insufficient_scope"
          },
          "404": {
            "description": "hold_not_found"
          },
          "409": {
            "description": "hold_already_settled"
          }
        }
      }
    },
    "/api/v1/hub-wallets/holds/{id}/release": {
      "post": {
        "summary": "Release a hold without capturing",
        "description": "Returns the reserved funds to the wallet. Same scope as capture (`hub_wallets:capture`) — whoever can settle can also release. Idempotent.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Released"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "insufficient_scope"
          },
          "404": {
            "description": "hold_not_found"
          }
        }
      }
    },
    "/api/v1/hub-wallets/refunds": {
      "post": {
        "summary": "Refund a captured wallet charge",
        "description": "Reverses a previously captured wallet debit (full or partial). Idempotent on `idempotency_key`. Scope: `hub_wallets:refund`.",
        "responses": {
          "201": {
            "description": "Refund posted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "insufficient_scope"
          },
          "409": {
            "description": "refund_exceeds_captured"
          }
        }
      }
    },
    "/api/v1/hub-wallets/spend-authorisations/requests": {
      "post": {
        "summary": "Open a spend-authorisation consent request",
        "description": "Creates a consent request the user signs in the Hub consent UI. Once approved, the resulting spend-authorisation can be used as the parent for `/holds`. Scope: `hub_wallets:hold`. The `requests/{id}` GET variant is for polling status.",
        "responses": {
          "201": {
            "description": "Consent request created"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "insufficient_scope"
          }
        }
      }
    },
    "/api/v1/orders": {
      "post": {
        "summary": "Place a partner order",
        "description": "Place an order with a partner provider. Requires `Idempotency-Key` (8..200 chars).",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sku_code",
                  "amount"
                ],
                "properties": {
                  "sku_code": {
                    "type": "string",
                    "example": "partner_a.product_a.usd"
                  },
                  "amount": {
                    "type": "number",
                    "example": 25
                  },
                  "identity_user_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "external_order_id": {
                    "type": "string"
                  },
                  "retail_amount_override": {
                    "type": "number"
                  },
                  "retail_currency_override": {
                    "type": "string"
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Order placed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "insufficient_scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/identity/lookup": {
      "get": {
        "summary": "Lookup identity by email/phone",
        "parameters": [
          {
            "name": "email",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "email"
            }
          },
          {
            "name": "phone",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityUser"
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/identity/users": {
      "post": {
        "summary": "Create identity user",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "display_name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityUser"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/identity/users/{id}": {
      "get": {
        "summary": "Get identity user",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityUser"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/ledger/transactions": {
      "get": {
        "summary": "List ledger transactions",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    }
  }
}