{
 "openapi": "3.0.3",
 "info": {
  "title": "MARBIN Core API",
  "version": "1.4",
  "description": "MARBIN's Shariah-first research engine, read-only: the Shariah screen with its ratios and method, purification, zakat, the market reading, entry signals and the long-term model portfolio. Data and methodology, not investment advice; the screen is mechanical, not a fatwa. The API never trades and never holds anyone's broker key. A white-label bank's own systems also run its customers here (the customer and account API, below): machine sign-in, one bank only."
 },
 "servers": [
  {
   "url": "https://api.marbin.co.uk"
  }
 ],
 "components": {
  "securitySchemes": {
   "bearer": {
    "type": "http",
    "scheme": "bearer",
    "description": "Authorization: Bearer mk_\u2026 (one key per client)"
   },
   "oauth2": {
    "type": "oauth2",
    "description": "A bank's own systems (the customer and account API): client credentials for a 15-minute token, sent as Authorization: Bearer mat_\u2026",
    "flows": {
     "clientCredentials": {
      "tokenUrl": "https://bank-api.marbin.co.uk/v1/oauth/token",
      "scopes": {
       "customers:read": "list customers, one customer and their accounts",
       "customers:write": "onboard a customer; record the bank's own customer check",
       "accounts:read": "an account's holdings and performance",
       "accounts:pause": "pause and resume an account's trading"
      }
     }
    }
   },
   "basic": {
    "type": "http",
    "scheme": "basic",
    "description": "client id and secret, for /v1/oauth/token only"
   }
  },
  "schemas": {
   "Error": {
    "type": "object",
    "properties": {
     "error": {
      "type": "object",
      "properties": {
       "code": {
        "type": "string"
       },
       "message": {
        "type": "string"
       }
      },
      "required": [
       "code",
       "message"
      ]
     },
     "request_id": {
      "type": "string"
     },
     "api_version": {
      "type": "string"
     }
    },
    "required": [
     "error",
     "request_id"
    ]
   }
  }
 },
 "paths": {
  "/v1/health": {
   "get": {
    "summary": "Is the engine running?",
    "description": "Heartbeat, last scan and stocks covered. No key needed.",
    "parameters": [],
    "x-widget-key": false,
    "x-example": "/v1/health",
    "responses": {
     "200": {
      "description": "OK"
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/universe": {
   "get": {
    "summary": "Stocks covered",
    "description": "Every stock the engine follows, with its Shariah status and when it was last checked.",
    "parameters": [],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": false,
    "x-example": "/v1/universe",
    "responses": {
     "200": {
      "description": "OK"
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/screen/{symbol}": {
   "get": {
    "summary": "Shariah screen for one stock",
    "description": "The verdict with its evidence: business activity, debt and cash against the 24-month average market value, interest income against revenue, the limits used and the method. A key carrying the client's own Shariah limits gets the verdict decided by those limits.",
    "parameters": [
     {
      "name": "symbol",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "example": "AAPL"
     }
    ],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": true,
    "x-example": "/v1/screen/AAPL",
    "responses": {
     "200": {
      "description": "OK"
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/screen": {
   "get": {
    "summary": "Shariah screen for many stocks",
    "description": "Up to 50 symbols a call, in one answer.",
    "parameters": [
     {
      "name": "symbols",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string"
      },
      "example": "AAPL,MSFT,NVDA"
     }
    ],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": true,
    "x-example": "/v1/screen?symbols=AAPL,MSFT,NVDA",
    "responses": {
     "200": {
      "description": "OK"
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/purification/{symbol}": {
   "get": {
    "summary": "Purification",
    "description": "The share of each dividend to give away (the company's interest income as a share of its revenue). Add dividend= to get the amount.",
    "parameters": [
     {
      "name": "symbol",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "example": "AAPL"
     },
     {
      "name": "dividend",
      "in": "query",
      "schema": {
       "type": "number"
      },
      "example": 120
     }
    ],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": true,
    "x-example": "/v1/purification/AAPL?dividend=120",
    "responses": {
     "200": {
      "description": "OK"
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/zakat": {
   "get": {
    "summary": "Zakat on a portfolio",
    "description": "Zakat at 2.5% two common ways: every share at market value (market_value), and each share at its company's own zakatable assets -- cash, short-term investments, receivables and inventory per share from its latest balance sheet, against its price (company_assets; AAOIFI Shariah Standard 35), a quarter of the value where a company's figures are missing (named in proxied). The live nisab (85 g gold, 595 g silver) in the currency asked for. Each holding's Shariah status and zakatable share come back too. quarter_rule (the older flat 25%) is kept for version 1.3's clients.",
    "parameters": [
     {
      "name": "holdings",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "example": "AAPL:5000,MSFT:2500"
     },
     {
      "name": "cash",
      "in": "query",
      "schema": {
       "type": "number"
      },
      "example": 1000
     },
     {
      "name": "currency",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "GBP",
        "USD",
        "EUR"
       ]
      },
      "example": "GBP"
     },
     {
      "name": "standard",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "silver",
        "gold"
       ]
      },
      "example": "silver"
     }
    ],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": true,
    "x-example": "/v1/zakat?holdings=AAPL:5000,MSFT:2500&cash=1000",
    "responses": {
     "200": {
      "description": "OK"
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/market": {
   "get": {
    "summary": "Market reading",
    "description": "Index trends against their 200-day averages, and the VIX fear gauge.",
    "parameters": [],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": false,
    "x-example": "/v1/market",
    "responses": {
     "200": {
      "description": "OK"
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/signals": {
   "get": {
    "summary": "Entry signals",
    "description": "Stocks that passed every gate at the last scan, best score first.",
    "parameters": [],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": false,
    "x-example": "/v1/signals",
    "responses": {
     "200": {
      "description": "OK"
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/model/core": {
   "get": {
    "summary": "Long-term model portfolio",
    "description": "The holdings MARBIN's rules pick today, with the rules.",
    "parameters": [],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": false,
    "x-example": "/v1/model/core",
    "responses": {
     "200": {
      "description": "OK"
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/filings/{symbol}": {
   "get": {
    "summary": "Company filings red flags",
    "description": "US companies: SEC 8-K red flags (bankruptcy, triggering events, delisting, restated results) and insider buying and selling over 90 days, sales after exercising options counted apart. The engine checks its watchlist twice a day; covered=false for a company with no SEC filings. Shown, not acted on.",
    "parameters": [
     {
      "name": "symbol",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "example": "AAPL"
     }
    ],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": false,
    "x-example": "/v1/filings/AAPL",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "api_version": {
           "type": "string"
          },
          "notice": {
           "type": "string"
          },
          "checks": {
           "type": "integer"
          },
          "filings": {
           "type": "object",
           "properties": {
            "symbol": {
             "type": "string"
            },
            "name": {
             "type": "string"
            },
            "checked_at": {
             "type": "string",
             "format": "date-time"
            },
            "covered": {
             "type": "boolean",
             "description": "false: not a US filer, or not checked yet"
            },
            "red_flags": {
             "type": "array",
             "items": {
              "type": "string"
             },
             "description": "8-K items 1.03, 2.04, 3.01, 4.02 in the window"
            },
            "recent_8k": {
             "type": "array",
             "items": {
              "type": "object",
              "properties": {
               "date": {
                "type": "string",
                "format": "date"
               },
               "items": {
                "type": "array",
                "items": {
                 "type": "string"
                }
               },
               "what": {
                "type": "array",
                "items": {
                 "type": "string"
                }
               }
              }
             }
            },
            "insiders": {
             "type": "object",
             "properties": {
              "days": {
               "type": "integer"
              },
              "buyers": {
               "type": "integer"
              },
              "sellers": {
               "type": "integer"
              },
              "bought_usd": {
               "type": "integer"
              },
              "sold_usd": {
               "type": "integer"
              },
              "sold_outright_usd": {
               "type": "integer"
              },
              "sold_after_options_usd": {
               "type": "integer"
              },
              "top_seller": {
               "type": "object",
               "properties": {
                "name": {
                 "type": "string"
                },
                "usd": {
                 "type": "integer"
                },
                "after_options": {
                 "type": "boolean"
                },
                "pct_of_holding": {
                 "type": "integer",
                 "nullable": true
                }
               },
               "nullable": true
              }
             }
            },
            "about": {
             "type": "string"
            }
           },
           "required": [
            "symbol",
            "covered",
            "red_flags",
            "recent_8k",
            "insiders"
           ]
          }
         },
         "required": [
          "filings"
         ]
        },
        "example": {
         "filings": {
          "symbol": "AAPL",
          "name": "Apple Inc.",
          "checked_at": "2026-10-21T13:05:00Z",
          "covered": true,
          "red_flags": [],
          "recent_8k": [
           {
            "date": "2026-10-02",
            "items": [
             "2.02"
            ],
            "what": [
             "Results of operations"
            ]
           }
          ],
          "insiders": {
           "days": 90,
           "buyers": 0,
           "sellers": 3,
           "bought_usd": 0,
           "sold_usd": 41250000,
           "sold_outright_usd": 2250000,
           "sold_after_options_usd": 39000000,
           "top_seller": {
            "name": "Example Officer",
            "usd": 39000000,
            "after_options": true,
            "pct_of_holding": 0
           }
          },
          "about": "From SEC EDGAR: 8-K items 1.03 (bankruptcy), 2.04 (triggering events), 3.01 (delisting) and 4.02 (non-reliance on past results) are red flags; insiders split into sales after exercising options and sales outright."
         },
         "api_version": "1.4",
         "notice": "\u2026",
         "checks": 1
        }
       }
      }
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/track-record": {
   "get": {
    "summary": "How MARBIN's forecasts have done",
    "description": "From the forecast ledger: each kind of forecast (the return model, the MARBIN score, analysts' price targets, the risk score) at each horizon, checked against what happened -- rank correlation with the outcome (mean_ic), top-minus-bottom fifth (spread_pct), analysts' hit rates. Past results are no guide to the future.",
    "parameters": [],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": false,
    "x-example": "/v1/track-record",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "api_version": {
           "type": "string"
          },
          "notice": {
           "type": "string"
          },
          "checks": {
           "type": "integer"
          },
          "track_record": {
           "type": "object",
           "properties": {
            "as_of": {
             "type": "string",
             "format": "date-time"
            },
            "kinds": {
             "type": "array",
             "items": {
              "type": "object",
              "properties": {
               "kind": {
                "type": "string",
                "enum": [
                 "model",
                 "score",
                 "analyst",
                 "risk"
                ]
               },
               "horizon": {
                "type": "integer",
                "description": "trading days after the forecast"
               },
               "n": {
                "type": "integer",
                "description": "forecasts checked against what happened"
               },
               "pending": {
                "type": "integer",
                "description": "forecasts not yet due"
               },
               "dates": {
                "type": "integer"
               },
               "mean_ic": {
                "type": "number",
                "nullable": true
               },
               "ic_t": {
                "type": "number",
                "nullable": true
               },
               "ic_positive_pct": {
                "type": "number",
                "nullable": true
               },
               "spread_pct": {
                "type": "number",
                "nullable": true
               },
               "fifths": {
                "type": "array",
                "items": {
                 "type": "object",
                 "properties": {
                  "forecast": {
                   "type": "number"
                  },
                  "actual": {
                   "type": "number"
                  },
                  "n": {
                   "type": "integer"
                  }
                 }
                }
               },
               "mean_error_pct": {
                "type": "number"
               },
               "reached_pct": {
                "type": "number"
               },
               "direction_hit_pct": {
                "type": "number"
               },
               "implied_upside_pct": {
                "type": "number"
               },
               "actual_return_pct": {
                "type": "number"
               }
              },
              "required": [
               "kind",
               "horizon",
               "n",
               "pending"
              ]
             },
             "description": "one row per kind and horizon; figures appear once 20 or more forecasts are checked"
            },
            "about": {
             "type": "object",
             "additionalProperties": {
              "type": "string"
             }
            },
            "note": {
             "type": "string",
             "description": "only when there is no forecast ledger yet"
            }
           },
           "required": [
            "kinds"
           ]
          }
         },
         "required": [
          "track_record"
         ]
        },
        "example": {
         "track_record": {
          "as_of": "2026-10-21T06:30:00Z",
          "kinds": [
           {
            "kind": "score",
            "horizon": 21,
            "n": 4210,
            "pending": 610,
            "dates": 58,
            "mean_ic": 0.041,
            "ic_t": 2.1,
            "ic_positive_pct": 62.1,
            "spread_pct": 1.18,
            "fifths": [
             {
              "forecast": 42.0,
              "actual": -0.004,
              "n": 842
             },
             {
              "forecast": 81.5,
              "actual": 0.009,
              "n": 842
             }
            ]
           },
           {
            "kind": "analyst",
            "horizon": 63,
            "n": 3900,
            "pending": 1200,
            "mean_error_pct": -6.2,
            "reached_pct": 31.4,
            "direction_hit_pct": 64.0,
            "implied_upside_pct": 12.3,
            "actual_return_pct": 4.1
           }
          ],
          "about": {
           "mean_ic": "rank correlation of forecast and outcome, averaged over dates (above 0 = skill)"
          }
         },
         "api_version": "1.4",
         "notice": "\u2026",
         "checks": 1
        }
       }
      }
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/history": {
   "get": {
    "summary": "What the history holds",
    "description": "The dates covered, the stocks, the exchange-rate pairs and every field a stock's daily reading has.",
    "parameters": [],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": false,
    "x-example": "/v1/history",
    "responses": {
     "200": {
      "description": "OK"
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/history/fields": {
   "get": {
    "summary": "What every history field means",
    "description": "Each field a stock's daily reading can carry, with its unit, and the fields sent when fields= isn't given.",
    "parameters": [],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": false,
    "x-example": "/v1/history/fields",
    "responses": {
     "200": {
      "description": "OK"
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/history/{symbol}": {
   "get": {
    "summary": "One stock's daily readings",
    "description": "The engine's closing reading of a stock on each day: price and technicals, MARBIN score and risk score (with their parts), stop and target, forecast, analysts, the Shariah verdict and ratios, fundamentals, filings red flags and insider selling. fields= picks columns (or all); 30 days by default, up to 400.",
    "parameters": [
     {
      "name": "symbol",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "example": "AAPL"
     },
     {
      "name": "from",
      "in": "query",
      "schema": {
       "type": "string",
       "format": "date"
      }
     },
     {
      "name": "to",
      "in": "query",
      "schema": {
       "type": "string",
       "format": "date"
      }
     },
     {
      "name": "fields",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "example": "score,risk_score,debt_pct"
     }
    ],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": false,
    "x-example": "/v1/history/AAPL?from=2026-10-01&fields=score,risk_score,shariah_passed",
    "responses": {
     "200": {
      "description": "OK"
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/history/day/{date}": {
   "get": {
    "summary": "Every stock on one day",
    "description": "The whole universe's reading on one day.",
    "parameters": [
     {
      "name": "date",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "format": "date"
      }
     }
    ],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": false,
    "x-example": "/v1/history/day/2026-10-02?fields=score,risk_score",
    "responses": {
     "200": {
      "description": "OK"
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/history/market": {
   "get": {
    "summary": "The market by day",
    "description": "The VIX and each index against its 200-day average.",
    "parameters": [],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": false,
    "x-example": "/v1/history/market?from=2026-10-01",
    "responses": {
     "200": {
      "description": "OK"
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/fx/{pair}": {
   "get": {
    "summary": "Exchange rates by day",
    "description": "Daily closing rates, e.g. GBPUSD.",
    "parameters": [
     {
      "name": "pair",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "example": "GBPUSD"
     }
    ],
    "security": [
     {
      "bearer": []
     }
    ],
    "x-widget-key": false,
    "x-example": "/v1/fx/GBPUSD?from=2026-09-01",
    "responses": {
     "200": {
      "description": "OK"
     },
     "400": {
      "description": "A bad parameter"
     },
     "401": {
      "description": "No valid key"
     },
     "403": {
      "description": "This key is limited to other groups (its scopes)"
     },
     "404": {
      "description": "Not covered"
     },
     "429": {
      "description": "Over this key's requests a minute"
     }
    }
   }
  },
  "/v1/oauth/token": {
   "post": {
    "summary": "Machine sign-in: an access token",
    "description": "OAuth 2.0 client credentials (RFC 6749 section 4.4). Send the client id and secret MARBIN gave the bank -- HTTP Basic (preferred) or client_id and client_secret in the form -- with grant_type=client_credentials and, optionally, scope (space-separated, at most the client's). The token lives 15 minutes; ask for a new one when it runs out. Errors here are RFC 6749's: {error, error_description}.",
    "parameters": [],
    "security": [
     {
      "basic": []
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/x-www-form-urlencoded": {
       "schema": {
        "type": "object",
        "properties": {
         "grant_type": {
          "type": "string",
          "enum": [
           "client_credentials"
          ]
         },
         "scope": {
          "type": "string"
         },
         "client_id": {
          "type": "string"
         },
         "client_secret": {
          "type": "string"
         }
        },
        "required": [
         "grant_type"
        ]
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "A token",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "access_token": {
           "type": "string"
          },
          "token_type": {
           "type": "string",
           "enum": [
            "Bearer"
           ]
          },
          "expires_in": {
           "type": "integer"
          },
          "scope": {
           "type": "string"
          }
         },
         "required": [
          "access_token",
          "token_type",
          "expires_in",
          "scope"
         ]
        },
        "example": {
         "access_token": "mat_\u2026",
         "token_type": "Bearer",
         "expires_in": 900,
         "scope": "accounts:read customers:read"
        }
       }
      }
     },
     "400": {
      "description": "unsupported_grant_type, invalid_scope or invalid_request"
     },
     "401": {
      "description": "invalid_client: unknown client or wrong secret"
     },
     "429": {
      "description": "slow_down (see Retry-After): too many tokens for this client a minute, too many requests from this address, or too many wrong secrets from it (a backoff, doubling up to 15 minutes; the right secret from another address still works)"
     },
     "405": {
      "description": "method_not_allowed: POST only (Allow: POST)"
     }
    }
   },
   "servers": [
    {
     "url": "https://bank-api.marbin.co.uk",
     "description": "the bank API (its own address)"
    }
   ]
  },
  "/v1/customers": {
   "get": {
    "summary": "The bank's customers",
    "description": "Paged, oldest id first: limit (1-200, default 50) and the next_cursor of the page before. Filters: status, kind (live or practice: the main account's), email, external_id. Removed customers only with status=removed. Scope: customers:read.",
    "parameters": [
     {
      "name": "status",
      "in": "query",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "kind",
      "in": "query",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "email",
      "in": "query",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "external_id",
      "in": "query",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "limit",
      "in": "query",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "cursor",
      "in": "query",
      "schema": {
       "type": "string"
      }
     }
    ],
    "security": [
     {
      "oauth2": [
       "customers:read"
      ]
     }
    ],
    "x-scope": "customers:read",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "customers": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "id": {
              "type": "string"
             },
             "name": {
              "type": "string"
             },
             "email": {
              "type": "string"
             },
             "status": {
              "type": "string",
              "enum": [
               "invited",
               "registered",
               "approved",
               "paused",
               "removed"
              ]
             },
             "status_about": {
              "type": "string"
             },
             "external_id": {
              "type": "string",
              "nullable": true,
              "description": "the bank's own customer number"
             },
             "bank_check": {
              "type": "object",
              "properties": {
               "reference": {
                "type": "string"
               },
               "provider": {
                "type": "string"
               },
               "checked_on": {
                "type": "string",
                "format": "date"
               },
               "recorded_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
               }
              },
              "nullable": true
             },
             "sign_in_access": {
              "type": "string",
              "nullable": true,
              "enum": [
               "pending",
               "granted",
               "failed",
               "no_portal",
               null
              ],
              "description": "the customer's email on the bank portal's sign-in list: pending until MARBIN adds it; no_portal while the bank has no portal address"
             },
             "invited_at": {
              "type": "string",
              "format": "date-time",
              "nullable": true
             },
             "registered_at": {
              "type": "string",
              "format": "date-time",
              "nullable": true
             },
             "approved_at": {
              "type": "string",
              "format": "date-time",
              "nullable": true
             },
             "accounts": {
              "type": "array",
              "items": {
               "type": "object",
               "properties": {
                "id": {
                 "type": "string"
                },
                "kind": {
                 "type": "string",
                 "enum": [
                  "live",
                  "practice"
                 ],
                 "description": "live = real money; practice = not real money. Never add the two together."
                },
                "status": {
                 "type": "string",
                 "enum": [
                  "registered",
                  "approved",
                  "paused",
                  "removed"
                 ]
                },
                "broker": {
                 "type": "string"
                },
                "account_type": {
                 "type": "string",
                 "nullable": true,
                 "description": "isa or invest"
                },
                "risk_profile": {
                 "type": "string",
                 "nullable": true,
                 "description": "the customer's chosen risk profile"
                },
                "currency": {
                 "type": "string",
                 "nullable": true
                },
                "label": {
                 "type": "string",
                 "nullable": true
                },
                "waiting_for_key": {
                 "type": "boolean"
                },
                "account_value": {
                 "type": "number",
                 "nullable": true,
                 "description": "the broker account's total, last read"
                },
                "registered_at": {
                 "type": "string",
                 "format": "date-time",
                 "nullable": true
                },
                "approved_at": {
                 "type": "string",
                 "format": "date-time",
                 "nullable": true
                },
                "paused_at": {
                 "type": "string",
                 "format": "date-time",
                 "nullable": true
                }
               },
               "required": [
                "id",
                "kind",
                "status"
               ]
              }
             }
            },
            "required": [
             "id",
             "name",
             "email",
             "status",
             "accounts"
            ]
           }
          },
          "limit": {
           "type": "integer"
          },
          "next_cursor": {
           "type": "string",
           "nullable": true
          },
          "request_id": {
           "type": "string"
          },
          "api_version": {
           "type": "string"
          }
         },
         "required": [
          "customers",
          "next_cursor"
         ]
        },
        "example": {
         "customers": [
          {
           "id": "cus_3f9a1c07e52b84d6",
           "name": "Aisha Khan",
           "email": "aisha@example.org",
           "status": "invited",
           "status_about": "invited; hasn't signed in and connected a broker yet",
           "external_id": "CIF-004211",
           "bank_check": {
            "reference": "KYC-2026-88123",
            "provider": "Bank onboarding",
            "checked_on": "2026-10-20",
            "recorded_at": "2026-10-22T09:14:03Z"
           },
           "sign_in_access": "pending",
           "invited_at": "2026-10-22T09:14:03Z",
           "registered_at": null,
           "approved_at": null,
           "accounts": []
          }
         ],
         "limit": 50,
         "next_cursor": null,
         "request_id": "req_3f2a9c0d1e2b4a5f",
         "api_version": "1.4"
        }
       }
      }
     },
     "400": {
      "description": "A bad parameter or body",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "No valid access token",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "403": {
      "description": "The token hasn't this scope, or the client's role may not",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "Not this bank's, or doesn't exist (the same answer)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Over this client's requests a minute",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   },
   "post": {
    "summary": "Onboard a customer",
    "description": "An invitation in the bank's name, pending until approved: emailed to the customer in the bank's name (powered by MARBIN) unless send_invitation is false, and their email queued for the bank portal's sign-in list (sign_in_access). The customer signs in on the bank's portal with this email, connects their own broker account and makes their choices, then the bank approves them. Record the bank's own customer check (bank_check) and customer number (external_id) here if they're ready. One person, one provider: an email already with MARBIN or another provider is refused (409 already_with_another_provider, never which), and so is a new customer over the bank's customer limit (409 over_customer_limit). Scope: customers:write.",
    "parameters": [
     {
      "name": "Idempotency-Key",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "a new unique value (e.g. a UUID) per request; a retry with the same key and request gets the first answer again (24 hours); the same key with a different request is refused (422)"
     }
    ],
    "security": [
     {
      "oauth2": [
       "customers:write"
      ]
     }
    ],
    "x-scope": "customers:write",
    "responses": {
     "201": {
      "description": "Created",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "customer": {
           "type": "object",
           "properties": {
            "id": {
             "type": "string"
            },
            "name": {
             "type": "string"
            },
            "email": {
             "type": "string"
            },
            "status": {
             "type": "string",
             "enum": [
              "invited",
              "registered",
              "approved",
              "paused",
              "removed"
             ]
            },
            "status_about": {
             "type": "string"
            },
            "external_id": {
             "type": "string",
             "nullable": true,
             "description": "the bank's own customer number"
            },
            "bank_check": {
             "type": "object",
             "properties": {
              "reference": {
               "type": "string"
              },
              "provider": {
               "type": "string"
              },
              "checked_on": {
               "type": "string",
               "format": "date"
              },
              "recorded_at": {
               "type": "string",
               "format": "date-time",
               "nullable": true
              }
             },
             "nullable": true
            },
            "sign_in_access": {
             "type": "string",
             "nullable": true,
             "enum": [
              "pending",
              "granted",
              "failed",
              "no_portal",
              null
             ],
             "description": "the customer's email on the bank portal's sign-in list: pending until MARBIN adds it; no_portal while the bank has no portal address"
            },
            "invited_at": {
             "type": "string",
             "format": "date-time",
             "nullable": true
            },
            "registered_at": {
             "type": "string",
             "format": "date-time",
             "nullable": true
            },
            "approved_at": {
             "type": "string",
             "format": "date-time",
             "nullable": true
            },
            "accounts": {
             "type": "array",
             "items": {
              "type": "object",
              "properties": {
               "id": {
                "type": "string"
               },
               "kind": {
                "type": "string",
                "enum": [
                 "live",
                 "practice"
                ],
                "description": "live = real money; practice = not real money. Never add the two together."
               },
               "status": {
                "type": "string",
                "enum": [
                 "registered",
                 "approved",
                 "paused",
                 "removed"
                ]
               },
               "broker": {
                "type": "string"
               },
               "account_type": {
                "type": "string",
                "nullable": true,
                "description": "isa or invest"
               },
               "risk_profile": {
                "type": "string",
                "nullable": true,
                "description": "the customer's chosen risk profile"
               },
               "currency": {
                "type": "string",
                "nullable": true
               },
               "label": {
                "type": "string",
                "nullable": true
               },
               "waiting_for_key": {
                "type": "boolean"
               },
               "account_value": {
                "type": "number",
                "nullable": true,
                "description": "the broker account's total, last read"
               },
               "registered_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
               },
               "approved_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
               },
               "paused_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
               }
              },
              "required": [
               "id",
               "kind",
               "status"
              ]
             }
            }
           },
           "required": [
            "id",
            "name",
            "email",
            "status",
            "accounts"
           ]
          },
          "next_steps": {
           "type": "string"
          },
          "invitation_email": {
           "type": "string",
           "enum": [
            "sent",
            "failed",
            "not_sent"
           ]
          },
          "brokers": {
           "type": "object",
           "properties": {
            "allowed": {
             "type": "array",
             "items": {
              "type": "string"
             },
             "description": "the brokers the customer may connect (set by MARBIN for the bank)"
            },
            "default": {
             "type": "string",
             "nullable": true
            }
           }
          },
          "request_id": {
           "type": "string"
          },
          "api_version": {
           "type": "string"
          }
         },
         "required": [
          "customer"
         ]
        },
        "example": {
         "customer": {
          "id": "cus_3f9a1c07e52b84d6",
          "name": "Aisha Khan",
          "email": "aisha@example.org",
          "status": "invited",
          "status_about": "invited; hasn't signed in and connected a broker yet",
          "external_id": "CIF-004211",
          "bank_check": {
           "reference": "KYC-2026-88123",
           "provider": "Bank onboarding",
           "checked_on": "2026-10-20",
           "recorded_at": "2026-10-22T09:14:03Z"
          },
          "sign_in_access": "pending",
          "invited_at": "2026-10-22T09:14:03Z",
          "registered_at": null,
          "approved_at": null,
          "accounts": []
         },
         "next_steps": "Pending until approved: \u2026",
         "invitation_email": "sent",
         "brokers": {
          "allowed": [
           "trading212"
          ],
          "default": "trading212"
         },
         "request_id": "req_3f2a9c0d1e2b4a5f",
         "api_version": "1.4"
        }
       }
      }
     },
     "400": {
      "description": "A bad parameter or body",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "No valid access token",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "403": {
      "description": "The token hasn't this scope, or the client's role may not",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "Not this bank's, or doesn't exist (the same answer)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Over this client's requests a minute",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "409": {
      "description": "A customer with this email or external_id already exists (already_exists), the email is already with MARBIN or another provider (already_with_another_provider), the bank's customer limit is reached (over_customer_limit), or the bank may offer no broker its customers can connect (no_broker_allowed)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "422": {
      "description": "A field isn't valid, or the Idempotency-Key was used with a different request",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    },
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "name": {
          "type": "string"
         },
         "email": {
          "type": "string"
         },
         "kind": {
          "type": "string",
          "enum": [
           "live",
           "practice"
          ],
          "description": "default: live (dev: practice)"
         },
         "external_id": {
          "type": "string",
          "description": "the bank's own customer number (unique per bank)"
         },
         "bank_check": {
          "type": "object",
          "properties": {
           "reference": {
            "type": "string"
           },
           "provider": {
            "type": "string"
           },
           "checked_on": {
            "type": "string",
            "format": "date"
           }
          },
          "required": [
           "reference"
          ]
         },
         "send_invitation": {
          "type": "boolean",
          "description": "email the invitation in the bank's name (default true); false: the bank invites the customer itself"
         }
        },
        "required": [
         "name",
         "email"
        ]
       }
      }
     }
    }
   },
   "servers": [
    {
     "url": "https://bank-api.marbin.co.uk",
     "description": "the bank API (its own address)"
    }
   ]
  },
  "/v1/customers/batch": {
   "post": {
    "summary": "Onboard customers in bulk",
    "description": "Up to 500 customers in one call, each checked as POST /v1/customers checks one and as the partner portal's Invite in bulk checks a line: a bad email or name, the same email or external_id twice in the request, already the bank's customer, already with MARBIN or another provider (never which), over the bank's customer limit. The ready ones are invited at once (pending until approved, their sign-in access queued); their invitation emails go out in the background at about 2 a second (send_invitations: false to leave them to the bank). One result per customer, in the request's order: a rejected one changes nothing; the call answers 200 whatever the mix. A retry with the same Idempotency-Key gets the same answer and invites no one twice. Scope: customers:write.",
    "parameters": [
     {
      "name": "Idempotency-Key",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "a new unique value (e.g. a UUID) per request; a retry with the same key and request gets the first answer again (24 hours); the same key with a different request is refused (422)"
     }
    ],
    "security": [
     {
      "oauth2": [
       "customers:write"
      ]
     }
    ],
    "x-scope": "customers:write",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "results": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "index": {
              "type": "integer",
              "description": "the customer's place in the request's list, from 0"
             },
             "email": {
              "type": "string",
              "nullable": true
             },
             "outcome": {
              "type": "string",
              "enum": [
               "invited",
               "rejected"
              ]
             },
             "customer_id": {
              "type": "string"
             },
             "customer": {
              "type": "object",
              "properties": {
               "id": {
                "type": "string"
               },
               "name": {
                "type": "string"
               },
               "email": {
                "type": "string"
               },
               "status": {
                "type": "string",
                "enum": [
                 "invited",
                 "registered",
                 "approved",
                 "paused",
                 "removed"
                ]
               },
               "status_about": {
                "type": "string"
               },
               "external_id": {
                "type": "string",
                "nullable": true,
                "description": "the bank's own customer number"
               },
               "bank_check": {
                "type": "object",
                "properties": {
                 "reference": {
                  "type": "string"
                 },
                 "provider": {
                  "type": "string"
                 },
                 "checked_on": {
                  "type": "string",
                  "format": "date"
                 },
                 "recorded_at": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                 }
                },
                "nullable": true
               },
               "sign_in_access": {
                "type": "string",
                "nullable": true,
                "enum": [
                 "pending",
                 "granted",
                 "failed",
                 "no_portal",
                 null
                ],
                "description": "the customer's email on the bank portal's sign-in list: pending until MARBIN adds it; no_portal while the bank has no portal address"
               },
               "invited_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
               },
               "registered_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
               },
               "approved_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
               },
               "accounts": {
                "type": "array",
                "items": {
                 "type": "object",
                 "properties": {
                  "id": {
                   "type": "string"
                  },
                  "kind": {
                   "type": "string",
                   "enum": [
                    "live",
                    "practice"
                   ],
                   "description": "live = real money; practice = not real money. Never add the two together."
                  },
                  "status": {
                   "type": "string",
                   "enum": [
                    "registered",
                    "approved",
                    "paused",
                    "removed"
                   ]
                  },
                  "broker": {
                   "type": "string"
                  },
                  "account_type": {
                   "type": "string",
                   "nullable": true,
                   "description": "isa or invest"
                  },
                  "risk_profile": {
                   "type": "string",
                   "nullable": true,
                   "description": "the customer's chosen risk profile"
                  },
                  "currency": {
                   "type": "string",
                   "nullable": true
                  },
                  "label": {
                   "type": "string",
                   "nullable": true
                  },
                  "waiting_for_key": {
                   "type": "boolean"
                  },
                  "account_value": {
                   "type": "number",
                   "nullable": true,
                   "description": "the broker account's total, last read"
                  },
                  "registered_at": {
                   "type": "string",
                   "format": "date-time",
                   "nullable": true
                  },
                  "approved_at": {
                   "type": "string",
                   "format": "date-time",
                   "nullable": true
                  },
                  "paused_at": {
                   "type": "string",
                   "format": "date-time",
                   "nullable": true
                  }
                 },
                 "required": [
                  "id",
                  "kind",
                  "status"
                 ]
                }
               }
              },
              "required": [
               "id",
               "name",
               "email",
               "status",
               "accounts"
              ],
              "nullable": true
             },
             "invitation_email": {
              "type": "string",
              "nullable": true,
              "enum": [
               "queued",
               "not_sent",
               null
              ]
             },
             "error": {
              "type": "object",
              "properties": {
               "code": {
                "type": "string",
                "enum": [
                 "validation_error",
                 "duplicate_in_request",
                 "already_exists",
                 "already_with_another_provider",
                 "over_customer_limit"
                ]
               },
               "message": {
                "type": "string"
               }
              }
             }
            },
            "required": [
             "index",
             "outcome"
            ]
           }
          },
          "counts": {
           "type": "object",
           "properties": {
            "received": {
             "type": "integer"
            },
            "invited": {
             "type": "integer"
            },
            "rejected": {
             "type": "integer"
            }
           }
          },
          "batch_id": {
           "type": "string",
           "nullable": true,
           "description": "the background batch sending the emails (in the partner portal's Invite in bulk)"
          },
          "next_steps": {
           "type": "string"
          },
          "request_id": {
           "type": "string"
          },
          "api_version": {
           "type": "string"
          }
         },
         "required": [
          "results",
          "counts"
         ]
        },
        "example": {
         "results": [
          {
           "index": 0,
           "email": "aisha@example.org",
           "outcome": "invited",
           "customer_id": "cus_3f9a1c07e52b84d6",
           "customer": {
            "id": "cus_3f9a1c07e52b84d6",
            "name": "Aisha Khan",
            "email": "aisha@example.org",
            "status": "invited",
            "status_about": "invited; hasn't signed in and connected a broker yet",
            "external_id": "CIF-004211",
            "bank_check": {
             "reference": "KYC-2026-88123",
             "provider": "Bank onboarding",
             "checked_on": "2026-10-20",
             "recorded_at": "2026-10-22T09:14:03Z"
            },
            "sign_in_access": "pending",
            "invited_at": "2026-10-22T09:14:03Z",
            "registered_at": null,
            "approved_at": null,
            "accounts": []
           },
           "invitation_email": "queued"
          },
          {
           "index": 1,
           "email": "not-an-email",
           "outcome": "rejected",
           "customer": null,
           "invitation_email": null,
           "error": {
            "code": "validation_error",
            "message": "not a valid email address"
           }
          },
          {
           "index": 2,
           "email": "yusuf@example.org",
           "outcome": "rejected",
           "customer": null,
           "invitation_email": null,
           "error": {
            "code": "already_with_another_provider",
            "message": "already with MARBIN or another provider"
           }
          }
         ],
         "counts": {
          "received": 3,
          "invited": 1,
          "rejected": 2
         },
         "batch_id": "bat_9c2e41d07a5b3f18",
         "next_steps": "Each invited customer is pending until approved, as for POST /v1/customers. \u2026",
         "request_id": "req_3f2a9c0d1e2b4a5f",
         "api_version": "1.4"
        }
       }
      }
     },
     "400": {
      "description": "A bad parameter or body",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "No valid access token",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "403": {
      "description": "The token hasn't this scope, or the client's role may not",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "Not this bank's, or doesn't exist (the same answer)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Over this client's requests a minute",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "409": {
      "description": "The bank may offer no broker its customers can connect (no_broker_allowed): nobody is invited",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "413": {
      "description": "Over 512 KB",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "422": {
      "description": "Not a list of 1 to 500 customers, an unknown field, or the Idempotency-Key was used with a different request",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "501": {
      "description": "Two-person approval is on for bulk invitations and isn't available through the API yet",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    },
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "customers": {
          "type": "array",
          "minItems": 1,
          "maxItems": 500,
          "items": {
           "type": "object",
           "properties": {
            "name": {
             "type": "string"
            },
            "email": {
             "type": "string"
            },
            "kind": {
             "type": "string",
             "enum": [
              "live",
              "practice"
             ],
             "description": "default: live (dev: practice)"
            },
            "external_id": {
             "type": "string",
             "description": "the bank's own customer number (unique per bank)"
            },
            "bank_check": {
             "type": "object",
             "properties": {
              "reference": {
               "type": "string"
              },
              "provider": {
               "type": "string"
              },
              "checked_on": {
               "type": "string",
               "format": "date"
              }
             },
             "required": [
              "reference"
             ]
            }
           },
           "required": [
            "name",
            "email"
           ]
          }
         },
         "send_invitations": {
          "type": "boolean",
          "description": "email each invitation in the bank's name, in the background (default true)"
         }
        },
        "required": [
         "customers"
        ]
       }
      }
     }
    }
   },
   "servers": [
    {
     "url": "https://bank-api.marbin.co.uk",
     "description": "the bank API (its own address)"
    }
   ]
  },
  "/v1/customers/{customer_id}": {
   "get": {
    "summary": "One customer",
    "description": "With their accounts, each live or practice. Scope: customers:read.",
    "parameters": [
     {
      "name": "customer_id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "example": "cus_3f9a1c07e52b84d6"
     }
    ],
    "security": [
     {
      "oauth2": [
       "customers:read"
      ]
     }
    ],
    "x-scope": "customers:read",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "customer": {
           "type": "object",
           "properties": {
            "id": {
             "type": "string"
            },
            "name": {
             "type": "string"
            },
            "email": {
             "type": "string"
            },
            "status": {
             "type": "string",
             "enum": [
              "invited",
              "registered",
              "approved",
              "paused",
              "removed"
             ]
            },
            "status_about": {
             "type": "string"
            },
            "external_id": {
             "type": "string",
             "nullable": true,
             "description": "the bank's own customer number"
            },
            "bank_check": {
             "type": "object",
             "properties": {
              "reference": {
               "type": "string"
              },
              "provider": {
               "type": "string"
              },
              "checked_on": {
               "type": "string",
               "format": "date"
              },
              "recorded_at": {
               "type": "string",
               "format": "date-time",
               "nullable": true
              }
             },
             "nullable": true
            },
            "sign_in_access": {
             "type": "string",
             "nullable": true,
             "enum": [
              "pending",
              "granted",
              "failed",
              "no_portal",
              null
             ],
             "description": "the customer's email on the bank portal's sign-in list: pending until MARBIN adds it; no_portal while the bank has no portal address"
            },
            "invited_at": {
             "type": "string",
             "format": "date-time",
             "nullable": true
            },
            "registered_at": {
             "type": "string",
             "format": "date-time",
             "nullable": true
            },
            "approved_at": {
             "type": "string",
             "format": "date-time",
             "nullable": true
            },
            "accounts": {
             "type": "array",
             "items": {
              "type": "object",
              "properties": {
               "id": {
                "type": "string"
               },
               "kind": {
                "type": "string",
                "enum": [
                 "live",
                 "practice"
                ],
                "description": "live = real money; practice = not real money. Never add the two together."
               },
               "status": {
                "type": "string",
                "enum": [
                 "registered",
                 "approved",
                 "paused",
                 "removed"
                ]
               },
               "broker": {
                "type": "string"
               },
               "account_type": {
                "type": "string",
                "nullable": true,
                "description": "isa or invest"
               },
               "risk_profile": {
                "type": "string",
                "nullable": true,
                "description": "the customer's chosen risk profile"
               },
               "currency": {
                "type": "string",
                "nullable": true
               },
               "label": {
                "type": "string",
                "nullable": true
               },
               "waiting_for_key": {
                "type": "boolean"
               },
               "account_value": {
                "type": "number",
                "nullable": true,
                "description": "the broker account's total, last read"
               },
               "registered_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
               },
               "approved_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
               },
               "paused_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
               }
              },
              "required": [
               "id",
               "kind",
               "status"
              ]
             }
            }
           },
           "required": [
            "id",
            "name",
            "email",
            "status",
            "accounts"
           ]
          },
          "request_id": {
           "type": "string"
          },
          "api_version": {
           "type": "string"
          }
         },
         "required": [
          "customer"
         ]
        },
        "example": {
         "customer": {
          "id": "cus_3f9a1c07e52b84d6",
          "name": "Aisha Khan",
          "email": "aisha@example.org",
          "status": "invited",
          "status_about": "invited; hasn't signed in and connected a broker yet",
          "external_id": "CIF-004211",
          "bank_check": {
           "reference": "KYC-2026-88123",
           "provider": "Bank onboarding",
           "checked_on": "2026-10-20",
           "recorded_at": "2026-10-22T09:14:03Z"
          },
          "sign_in_access": "pending",
          "invited_at": "2026-10-22T09:14:03Z",
          "registered_at": null,
          "approved_at": null,
          "accounts": []
         },
         "request_id": "req_3f2a9c0d1e2b4a5f",
         "api_version": "1.4"
        }
       }
      }
     },
     "400": {
      "description": "A bad parameter or body",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "No valid access token",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "403": {
      "description": "The token hasn't this scope, or the client's role may not",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "Not this bank's, or doesn't exist (the same answer)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Over this client's requests a minute",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   },
   "servers": [
    {
     "url": "https://bank-api.marbin.co.uk",
     "description": "the bank API (its own address)"
    }
   ]
  },
  "/v1/customers/{customer_id}/bank-check": {
   "post": {
    "summary": "Record the bank's own customer check",
    "description": "Before approval (invited or registered): the reference of the bank's own customer check, who did it and when. Shown to the bank's staff; MARBIN's approval flow is unchanged. Scope: customers:write.",
    "parameters": [
     {
      "name": "customer_id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "example": "cus_3f9a1c07e52b84d6"
     },
     {
      "name": "Idempotency-Key",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "a new unique value (e.g. a UUID) per request; a retry with the same key and request gets the first answer again (24 hours); the same key with a different request is refused (422)"
     }
    ],
    "security": [
     {
      "oauth2": [
       "customers:write"
      ]
     }
    ],
    "x-scope": "customers:write",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "customer": {
           "type": "object",
           "properties": {
            "id": {
             "type": "string"
            },
            "name": {
             "type": "string"
            },
            "email": {
             "type": "string"
            },
            "status": {
             "type": "string",
             "enum": [
              "invited",
              "registered",
              "approved",
              "paused",
              "removed"
             ]
            },
            "status_about": {
             "type": "string"
            },
            "external_id": {
             "type": "string",
             "nullable": true,
             "description": "the bank's own customer number"
            },
            "bank_check": {
             "type": "object",
             "properties": {
              "reference": {
               "type": "string"
              },
              "provider": {
               "type": "string"
              },
              "checked_on": {
               "type": "string",
               "format": "date"
              },
              "recorded_at": {
               "type": "string",
               "format": "date-time",
               "nullable": true
              }
             },
             "nullable": true
            },
            "sign_in_access": {
             "type": "string",
             "nullable": true,
             "enum": [
              "pending",
              "granted",
              "failed",
              "no_portal",
              null
             ],
             "description": "the customer's email on the bank portal's sign-in list: pending until MARBIN adds it; no_portal while the bank has no portal address"
            },
            "invited_at": {
             "type": "string",
             "format": "date-time",
             "nullable": true
            },
            "registered_at": {
             "type": "string",
             "format": "date-time",
             "nullable": true
            },
            "approved_at": {
             "type": "string",
             "format": "date-time",
             "nullable": true
            },
            "accounts": {
             "type": "array",
             "items": {
              "type": "object",
              "properties": {
               "id": {
                "type": "string"
               },
               "kind": {
                "type": "string",
                "enum": [
                 "live",
                 "practice"
                ],
                "description": "live = real money; practice = not real money. Never add the two together."
               },
               "status": {
                "type": "string",
                "enum": [
                 "registered",
                 "approved",
                 "paused",
                 "removed"
                ]
               },
               "broker": {
                "type": "string"
               },
               "account_type": {
                "type": "string",
                "nullable": true,
                "description": "isa or invest"
               },
               "risk_profile": {
                "type": "string",
                "nullable": true,
                "description": "the customer's chosen risk profile"
               },
               "currency": {
                "type": "string",
                "nullable": true
               },
               "label": {
                "type": "string",
                "nullable": true
               },
               "waiting_for_key": {
                "type": "boolean"
               },
               "account_value": {
                "type": "number",
                "nullable": true,
                "description": "the broker account's total, last read"
               },
               "registered_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
               },
               "approved_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
               },
               "paused_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
               }
              },
              "required": [
               "id",
               "kind",
               "status"
              ]
             }
            }
           },
           "required": [
            "id",
            "name",
            "email",
            "status",
            "accounts"
           ]
          },
          "request_id": {
           "type": "string"
          },
          "api_version": {
           "type": "string"
          }
         },
         "required": [
          "customer"
         ]
        }
       }
      }
     },
     "400": {
      "description": "A bad parameter or body",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "No valid access token",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "403": {
      "description": "The token hasn't this scope, or the client's role may not",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "Not this bank's, or doesn't exist (the same answer)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Over this client's requests a minute",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "409": {
      "description": "Not in a state for this (e.g. already paused)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "422": {
      "description": "A field isn't valid, or the Idempotency-Key was used with a different request",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    },
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "reference": {
          "type": "string"
         },
         "provider": {
          "type": "string"
         },
         "checked_on": {
          "type": "string",
          "format": "date"
         }
        },
        "required": [
         "reference"
        ]
       }
      }
     }
    }
   },
   "servers": [
    {
     "url": "https://bank-api.marbin.co.uk",
     "description": "the bank API (its own address)"
    }
   ]
  },
  "/v1/accounts/{account_id}/holdings": {
   "get": {
    "summary": "What MARBIN holds in an account",
    "description": "Each holding and the totals after costs, in the account's currency: total_cost = paid + buying costs; gain = value - total_cost; if_sold_today also takes off a sale's estimated costs. The same figures as the customer's own page and app. Scope: accounts:read.",
    "parameters": [
     {
      "name": "account_id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "example": "cus_3f9a1c07e52b84d6"
     }
    ],
    "security": [
     {
      "oauth2": [
       "accounts:read"
      ]
     }
    ],
    "x-scope": "accounts:read",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "account_id": {
           "type": "string"
          },
          "kind": {
           "type": "string",
           "enum": [
            "live",
            "practice"
           ],
           "description": "live = real money; practice = not real money. Never add the two together."
          },
          "practice": {
           "type": "boolean"
          },
          "currency": {
           "type": "string"
          },
          "as_of": {
           "type": "string",
           "format": "date-time",
           "nullable": true
          },
          "holdings": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "symbol": {
              "type": "string"
             },
             "name": {
              "type": "string"
             },
             "kind": {
              "type": "string",
              "enum": [
               "fund",
               "share"
              ]
             },
             "horizon": {
              "type": "string",
              "enum": [
               "index_fund",
               "long_term",
               "short_term"
              ]
             },
             "quantity": {
              "type": "number"
             },
             "average_price": {
              "type": "number",
              "nullable": true
             },
             "price": {
              "type": "number",
              "nullable": true
             },
             "price_currency": {
              "type": "string"
             },
             "value": {
              "type": "number",
              "nullable": true
             },
             "paid": {
              "type": "number",
              "nullable": true
             },
             "buying_costs": {
              "type": "number",
              "nullable": true
             },
             "total_cost": {
              "type": "number",
              "nullable": true
             },
             "gain": {
              "type": "number",
              "nullable": true
             },
             "gain_pct": {
              "type": "number",
              "nullable": true
             },
             "buying_estimated": {
              "type": "number",
              "nullable": true
             },
             "selling_estimated": {
              "type": "number",
              "nullable": true
             },
             "if_sold_today": {
              "type": "number",
              "nullable": true
             }
            }
           }
          },
          "totals": {
           "type": "object",
           "properties": {
            "value": {
             "type": "number",
             "nullable": true
            },
            "paid": {
             "type": "number",
             "nullable": true
            },
            "buying_costs": {
             "type": "number",
             "nullable": true
            },
            "total_cost": {
             "type": "number",
             "nullable": true
            },
            "gain": {
             "type": "number",
             "nullable": true
            },
            "gain_pct": {
             "type": "number",
             "nullable": true
            },
            "buying_estimated": {
             "type": "number",
             "nullable": true
            },
            "selling_estimated": {
             "type": "number",
             "nullable": true
            },
            "if_sold_today": {
             "type": "number",
             "nullable": true
            },
            "gain_before_costs": {
             "type": "number",
             "nullable": true
            }
           }
          },
          "about": {
           "type": "string"
          },
          "request_id": {
           "type": "string"
          },
          "api_version": {
           "type": "string"
          }
         },
         "required": [
          "account_id",
          "kind",
          "practice",
          "holdings",
          "totals"
         ]
        },
        "example": {
         "account_id": "cus_3f9a1c07e52b84d6",
         "kind": "live",
         "practice": false,
         "currency": "GBP",
         "as_of": "2026-10-22T14:30:00Z",
         "holdings": [
          {
           "symbol": "HIUS.L",
           "name": "Shariah index fund",
           "kind": "fund",
           "horizon": "index_fund",
           "quantity": 120.0,
           "average_price": 10.4,
           "price": 10.9,
           "price_currency": "GBP",
           "value": 1308.0,
           "paid": 1248.0,
           "buying_costs": 0.0,
           "total_cost": 1248.0,
           "gain": 60.0,
           "gain_pct": 4.81,
           "buying_estimated": 0.0,
           "selling_estimated": 0.0,
           "if_sold_today": 60.0
          }
         ],
         "totals": {
          "value": 1308.0,
          "paid": 1248.0,
          "buying_costs": 0.0,
          "total_cost": 1248.0,
          "gain": 60.0,
          "gain_before_costs": 60.0,
          "buying_estimated": 0.0,
          "selling_estimated": 0.0,
          "if_sold_today": 60.0,
          "gain_pct": 4.81
         },
         "about": "\u2026",
         "request_id": "req_3f2a9c0d1e2b4a5f",
         "api_version": "1.4"
        }
       }
      }
     },
     "400": {
      "description": "A bad parameter or body",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "No valid access token",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "403": {
      "description": "The token hasn't this scope, or the client's role may not",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "Not this bank's, or doesn't exist (the same answer)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Over this client's requests a minute",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "409": {
      "description": "Not trading yet (invited or registered)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   },
   "servers": [
    {
     "url": "https://bank-api.marbin.co.uk",
     "description": "the bank API (its own address)"
    }
   ]
  },
  "/v1/accounts/{account_id}/performance": {
   "get": {
    "summary": "How an account has done",
    "description": "Time-weighted returns against funds the customer could hold instead (day, week, month, year to date, since start), and the money made: holdings after buying costs, sales net of selling costs. Past results are no guide to the future. Scope: accounts:read.",
    "parameters": [
     {
      "name": "account_id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "example": "cus_3f9a1c07e52b84d6"
     }
    ],
    "security": [
     {
      "oauth2": [
       "accounts:read"
      ]
     }
    ],
    "x-scope": "accounts:read",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "account_id": {
           "type": "string"
          },
          "kind": {
           "type": "string",
           "enum": [
            "live",
            "practice"
           ],
           "description": "live = real money; practice = not real money. Never add the two together."
          },
          "practice": {
           "type": "boolean"
          },
          "currency": {
           "type": "string"
          },
          "as_of": {
           "type": "string",
           "format": "date-time",
           "nullable": true
          },
          "returns": {
           "type": "object",
           "properties": {
            "periods": {
             "type": "array",
             "items": {
              "type": "string"
             }
            },
            "account_pct": {
             "type": "object",
             "properties": {
              "day": {
               "type": "number",
               "nullable": true
              },
              "week": {
               "type": "number",
               "nullable": true
              },
              "mtd": {
               "type": "number",
               "nullable": true
              },
              "ytd": {
               "type": "number",
               "nullable": true
              },
              "all": {
               "type": "number",
               "nullable": true
              }
             }
            },
            "benchmarks_pct": {
             "type": "object",
             "additionalProperties": {
              "type": "object",
              "properties": {
               "day": {
                "type": "number",
                "nullable": true
               },
               "week": {
                "type": "number",
                "nullable": true
               },
               "mtd": {
                "type": "number",
                "nullable": true
               },
               "ytd": {
                "type": "number",
                "nullable": true
               },
               "all": {
                "type": "number",
                "nullable": true
               }
              }
             }
            }
           },
           "nullable": true,
           "description": "time-weighted %, after the broker's charges; null before the first full day"
          },
          "money": {
           "type": "object",
           "properties": {
            "holdings": {
             "type": "object",
             "properties": {
              "value": {
               "type": "number",
               "nullable": true
              },
              "paid": {
               "type": "number",
               "nullable": true
              },
              "buying_costs": {
               "type": "number",
               "nullable": true
              },
              "total_cost": {
               "type": "number",
               "nullable": true
              },
              "gain": {
               "type": "number",
               "nullable": true
              },
              "gain_pct": {
               "type": "number",
               "nullable": true
              },
              "buying_estimated": {
               "type": "number",
               "nullable": true
              },
              "selling_estimated": {
               "type": "number",
               "nullable": true
              },
              "if_sold_today": {
               "type": "number",
               "nullable": true
              },
              "gain_before_costs": {
               "type": "number",
               "nullable": true
              }
             }
            },
            "sales": {
             "type": "object",
             "properties": {
              "paid": {
               "type": "number",
               "nullable": true
              },
              "buying_costs": {
               "type": "number",
               "nullable": true
              },
              "total_cost": {
               "type": "number",
               "nullable": true
              },
              "sold": {
               "type": "number",
               "nullable": true
              },
              "selling_costs": {
               "type": "number",
               "nullable": true
              },
              "net_proceeds": {
               "type": "number",
               "nullable": true
              },
              "gain": {
               "type": "number",
               "nullable": true
              },
              "gain_pct": {
               "type": "number",
               "nullable": true
              },
              "count": {
               "type": "integer"
              },
              "incomplete": {
               "type": "integer"
              }
             }
            }
           }
          },
          "about": {
           "type": "string"
          },
          "request_id": {
           "type": "string"
          },
          "api_version": {
           "type": "string"
          }
         },
         "required": [
          "account_id",
          "kind",
          "practice",
          "returns",
          "money"
         ]
        }
       }
      }
     },
     "400": {
      "description": "A bad parameter or body",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "No valid access token",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "403": {
      "description": "The token hasn't this scope, or the client's role may not",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "Not this bank's, or doesn't exist (the same answer)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Over this client's requests a minute",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "409": {
      "description": "Not trading yet (invited or registered)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   },
   "servers": [
    {
     "url": "https://bank-api.marbin.co.uk",
     "description": "the bank API (its own address)"
    }
   ]
  },
  "/v1/accounts/{account_id}/pause": {
   "post": {
    "summary": "Pause an account",
    "description": "No new trades; the stops on its holdings stay. One person is enough: a safety action never waits. Scope: accounts:pause.",
    "parameters": [
     {
      "name": "account_id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "example": "cus_3f9a1c07e52b84d6"
     },
     {
      "name": "Idempotency-Key",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "a new unique value (e.g. a UUID) per request; a retry with the same key and request gets the first answer again (24 hours); the same key with a different request is refused (422)"
     }
    ],
    "security": [
     {
      "oauth2": [
       "accounts:pause"
      ]
     }
    ],
    "x-scope": "accounts:pause",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "account": {
           "type": "object",
           "properties": {
            "id": {
             "type": "string"
            },
            "kind": {
             "type": "string",
             "enum": [
              "live",
              "practice"
             ],
             "description": "live = real money; practice = not real money. Never add the two together."
            },
            "status": {
             "type": "string",
             "enum": [
              "registered",
              "approved",
              "paused",
              "removed"
             ]
            },
            "broker": {
             "type": "string"
            },
            "account_type": {
             "type": "string",
             "nullable": true,
             "description": "isa or invest"
            },
            "risk_profile": {
             "type": "string",
             "nullable": true,
             "description": "the customer's chosen risk profile"
            },
            "currency": {
             "type": "string",
             "nullable": true
            },
            "label": {
             "type": "string",
             "nullable": true
            },
            "waiting_for_key": {
             "type": "boolean"
            },
            "account_value": {
             "type": "number",
             "nullable": true,
             "description": "the broker account's total, last read"
            },
            "registered_at": {
             "type": "string",
             "format": "date-time",
             "nullable": true
            },
            "approved_at": {
             "type": "string",
             "format": "date-time",
             "nullable": true
            },
            "paused_at": {
             "type": "string",
             "format": "date-time",
             "nullable": true
            }
           },
           "required": [
            "id",
            "kind",
            "status"
           ]
          },
          "reason": {
           "type": "string"
          },
          "request_id": {
           "type": "string"
          },
          "api_version": {
           "type": "string"
          }
         },
         "required": [
          "account"
         ]
        }
       }
      }
     },
     "400": {
      "description": "A bad parameter or body",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "No valid access token",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "403": {
      "description": "The token hasn't this scope, or the client's role may not",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "Not this bank's, or doesn't exist (the same answer)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Over this client's requests a minute",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "409": {
      "description": "Not in a state for this (e.g. already paused)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "422": {
      "description": "A field isn't valid, or the Idempotency-Key was used with a different request",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    },
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "reason": {
          "type": "string",
          "description": "up to 200 characters, kept in the audit log's answer"
         }
        }
       }
      }
     }
    }
   },
   "servers": [
    {
     "url": "https://bank-api.marbin.co.uk",
     "description": "the bank API (its own address)"
    }
   ]
  },
  "/v1/accounts/{account_id}/resume": {
   "post": {
    "summary": "Resume a paused account",
    "description": "Only a paused account (never an approval). Dual control -- one approver at MARBIN and one at the bank -- will apply here once it's switched on; today the client's bank role is the guard. Scope: accounts:pause.",
    "parameters": [
     {
      "name": "account_id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "example": "cus_3f9a1c07e52b84d6"
     },
     {
      "name": "Idempotency-Key",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "a new unique value (e.g. a UUID) per request; a retry with the same key and request gets the first answer again (24 hours); the same key with a different request is refused (422)"
     }
    ],
    "security": [
     {
      "oauth2": [
       "accounts:pause"
      ]
     }
    ],
    "x-scope": "accounts:pause",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "account": {
           "type": "object",
           "properties": {
            "id": {
             "type": "string"
            },
            "kind": {
             "type": "string",
             "enum": [
              "live",
              "practice"
             ],
             "description": "live = real money; practice = not real money. Never add the two together."
            },
            "status": {
             "type": "string",
             "enum": [
              "registered",
              "approved",
              "paused",
              "removed"
             ]
            },
            "broker": {
             "type": "string"
            },
            "account_type": {
             "type": "string",
             "nullable": true,
             "description": "isa or invest"
            },
            "risk_profile": {
             "type": "string",
             "nullable": true,
             "description": "the customer's chosen risk profile"
            },
            "currency": {
             "type": "string",
             "nullable": true
            },
            "label": {
             "type": "string",
             "nullable": true
            },
            "waiting_for_key": {
             "type": "boolean"
            },
            "account_value": {
             "type": "number",
             "nullable": true,
             "description": "the broker account's total, last read"
            },
            "registered_at": {
             "type": "string",
             "format": "date-time",
             "nullable": true
            },
            "approved_at": {
             "type": "string",
             "format": "date-time",
             "nullable": true
            },
            "paused_at": {
             "type": "string",
             "format": "date-time",
             "nullable": true
            }
           },
           "required": [
            "id",
            "kind",
            "status"
           ]
          },
          "reason": {
           "type": "string"
          },
          "request_id": {
           "type": "string"
          },
          "api_version": {
           "type": "string"
          }
         },
         "required": [
          "account"
         ]
        }
       }
      }
     },
     "400": {
      "description": "A bad parameter or body",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "No valid access token",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "403": {
      "description": "The token hasn't this scope, or the client's role may not",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "Not this bank's, or doesn't exist (the same answer)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Over this client's requests a minute",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "409": {
      "description": "Not in a state for this (e.g. already paused)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "422": {
      "description": "A field isn't valid, or the Idempotency-Key was used with a different request",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    },
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "reason": {
          "type": "string",
          "description": "up to 200 characters, kept in the audit log's answer"
         }
        }
       }
      }
     }
    }
   },
   "servers": [
    {
     "url": "https://bank-api.marbin.co.uk",
     "description": "the bank API (its own address)"
    }
   ]
  }
 },
 "x-webhooks": {
  "signature": {
   "header": "MARBIN-Signature",
   "format": "t=<unix seconds>,v1=<hex>",
   "v1": "HMAC-SHA256(endpoint secret, t + '.' + raw body)",
   "tolerance_seconds": 300
  },
  "envelope": {
   "type": "object",
   "properties": {
    "id": {
     "type": "string",
     "description": "evt_\u2026; the same on a retry"
    },
    "type": {
     "type": "string"
    },
    "created": {
     "type": "integer"
    },
    "api_version": {
     "type": "string"
    },
    "bank": {
     "type": "string"
    },
    "livemode": {
     "type": "boolean"
    },
    "data": {
     "type": "object"
    }
   }
  },
  "customer": {
   "type": "object",
   "properties": {
    "customer_id": {
     "type": "string",
     "pattern": "^cus_[0-9a-f]{16}$",
     "description": "random, never a name"
    },
    "customer_ref": {
     "type": "string",
     "nullable": true,
     "description": "the bank's own reference"
    }
   }
  },
  "events": {
   "customer.joined": {
    "description": "A customer finished registering (waiting for approval)",
    "names_a_customer": true
   },
   "customer.approved": {
    "description": "A customer was approved: trading starts",
    "names_a_customer": true
   },
   "account.paused": {
    "description": "A customer's trading was paused",
    "names_a_customer": true
   },
   "account.resumed": {
    "description": "A customer's trading was resumed",
    "names_a_customer": true
   },
   "trade.executed": {
    "description": "A buy or a sale was filled at the broker",
    "names_a_customer": true
   },
   "stop.hit": {
    "description": "A holding was sold by its protective stop",
    "names_a_customer": true
   },
   "alert": {
    "description": "Something on a customer's account needs a look",
    "names_a_customer": true
   },
   "customer.removed": {
    "description": "A customer was removed (or their invitation cancelled): MARBIN stops trading for them",
    "names_a_customer": true
   },
   "engine.paused": {
    "description": "MARBIN paused all trading (every customer): no new buys; stops stay at the broker",
    "names_a_customer": false
   },
   "engine.resumed": {
    "description": "MARBIN resumed all trading",
    "names_a_customer": false
   },
   "test.ping": {
    "description": "a test from the partner portal",
    "names_a_customer": true
   }
  },
  "retry_after_seconds": [
   60,
   300,
   1800,
   7200,
   18000,
   36000,
   86400
  ]
 }
}