bitgen-sdk 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. bitgen_sdk-1.0.0/.gitignore +8 -0
  2. bitgen_sdk-1.0.0/CHANGELOG.md +23 -0
  3. bitgen_sdk-1.0.0/PKG-INFO +66 -0
  4. bitgen_sdk-1.0.0/README.md +37 -0
  5. bitgen_sdk-1.0.0/pyproject.toml +74 -0
  6. bitgen_sdk-1.0.0/readme/concepts.md +128 -0
  7. bitgen_sdk-1.0.0/readme/configuration.md +74 -0
  8. bitgen_sdk-1.0.0/readme/errors.md +73 -0
  9. bitgen_sdk-1.0.0/readme/installation.md +39 -0
  10. bitgen_sdk-1.0.0/readme/media/activation.svg +58 -0
  11. bitgen_sdk-1.0.0/readme/media/deposit-flow.svg +70 -0
  12. bitgen_sdk-1.0.0/readme/media/order-buy.svg +43 -0
  13. bitgen_sdk-1.0.0/readme/media/order-sell.svg +47 -0
  14. bitgen_sdk-1.0.0/readme/media/purchase-flow.svg +61 -0
  15. bitgen_sdk-1.0.0/readme/media/sale-flow.svg +61 -0
  16. bitgen_sdk-1.0.0/readme/media/staking-position.svg +75 -0
  17. bitgen_sdk-1.0.0/readme/media/transaction-lifecycle.svg +61 -0
  18. bitgen_sdk-1.0.0/readme/media/webhook-delivery.svg +53 -0
  19. bitgen_sdk-1.0.0/readme/media/withdrawal-flow.svg +64 -0
  20. bitgen_sdk-1.0.0/readme/quick-start.md +90 -0
  21. bitgen_sdk-1.0.0/readme/resource/apikeys.md +99 -0
  22. bitgen_sdk-1.0.0/readme/resource/asset.md +119 -0
  23. bitgen_sdk-1.0.0/readme/resource/bank.md +155 -0
  24. bitgen_sdk-1.0.0/readme/resource/core.md +90 -0
  25. bitgen_sdk-1.0.0/readme/resource/custody.md +170 -0
  26. bitgen_sdk-1.0.0/readme/resource/customer.md +206 -0
  27. bitgen_sdk-1.0.0/readme/resource/staking.md +274 -0
  28. bitgen_sdk-1.0.0/readme/resource/trading.md +160 -0
  29. bitgen_sdk-1.0.0/readme/resource/transaction.md +109 -0
  30. bitgen_sdk-1.0.0/readme/resource/webhooks.md +311 -0
  31. bitgen_sdk-1.0.0/src/bitgen/__init__.py +18 -0
  32. bitgen_sdk-1.0.0/src/bitgen/_http/__init__.py +1 -0
  33. bitgen_sdk-1.0.0/src/bitgen/_http/base_url.py +42 -0
  34. bitgen_sdk-1.0.0/src/bitgen/_http/client.py +131 -0
  35. bitgen_sdk-1.0.0/src/bitgen/_http/timeout.py +21 -0
  36. bitgen_sdk-1.0.0/src/bitgen/_http/transport.py +113 -0
  37. bitgen_sdk-1.0.0/src/bitgen/_support/__init__.py +1 -0
  38. bitgen_sdk-1.0.0/src/bitgen/_support/amount.py +42 -0
  39. bitgen_sdk-1.0.0/src/bitgen/_support/asset_id.py +18 -0
  40. bitgen_sdk-1.0.0/src/bitgen/_support/constants.py +19 -0
  41. bitgen_sdk-1.0.0/src/bitgen/_support/path.py +17 -0
  42. bitgen_sdk-1.0.0/src/bitgen/_support/reference.py +19 -0
  43. bitgen_sdk-1.0.0/src/bitgen/_support/user_id.py +26 -0
  44. bitgen_sdk-1.0.0/src/bitgen/_support/values.py +55 -0
  45. bitgen_sdk-1.0.0/src/bitgen/client.py +109 -0
  46. bitgen_sdk-1.0.0/src/bitgen/constants.py +36 -0
  47. bitgen_sdk-1.0.0/src/bitgen/errors.py +35 -0
  48. bitgen_sdk-1.0.0/src/bitgen/models/__init__.py +197 -0
  49. bitgen_sdk-1.0.0/src/bitgen/models/_cast.py +161 -0
  50. bitgen_sdk-1.0.0/src/bitgen/models/apikeys.py +136 -0
  51. bitgen_sdk-1.0.0/src/bitgen/models/asset.py +205 -0
  52. bitgen_sdk-1.0.0/src/bitgen/models/bank.py +104 -0
  53. bitgen_sdk-1.0.0/src/bitgen/models/core.py +122 -0
  54. bitgen_sdk-1.0.0/src/bitgen/models/custody.py +148 -0
  55. bitgen_sdk-1.0.0/src/bitgen/models/customer.py +598 -0
  56. bitgen_sdk-1.0.0/src/bitgen/models/history.py +44 -0
  57. bitgen_sdk-1.0.0/src/bitgen/models/organization.py +42 -0
  58. bitgen_sdk-1.0.0/src/bitgen/models/staking.py +229 -0
  59. bitgen_sdk-1.0.0/src/bitgen/models/trading.py +137 -0
  60. bitgen_sdk-1.0.0/src/bitgen/models/transaction.py +165 -0
  61. bitgen_sdk-1.0.0/src/bitgen/models/webhooks.py +237 -0
  62. bitgen_sdk-1.0.0/src/bitgen/page.py +42 -0
  63. bitgen_sdk-1.0.0/src/bitgen/py.typed +0 -0
  64. bitgen_sdk-1.0.0/src/bitgen/resources/__init__.py +27 -0
  65. bitgen_sdk-1.0.0/src/bitgen/resources/apikeys.py +50 -0
  66. bitgen_sdk-1.0.0/src/bitgen/resources/asset.py +37 -0
  67. bitgen_sdk-1.0.0/src/bitgen/resources/bank.py +103 -0
  68. bitgen_sdk-1.0.0/src/bitgen/resources/core.py +42 -0
  69. bitgen_sdk-1.0.0/src/bitgen/resources/custody.py +86 -0
  70. bitgen_sdk-1.0.0/src/bitgen/resources/customer.py +133 -0
  71. bitgen_sdk-1.0.0/src/bitgen/resources/staking.py +129 -0
  72. bitgen_sdk-1.0.0/src/bitgen/resources/trading.py +100 -0
  73. bitgen_sdk-1.0.0/src/bitgen/resources/transaction.py +50 -0
  74. bitgen_sdk-1.0.0/src/bitgen/resources/webhooks.py +237 -0
  75. bitgen_sdk-1.0.0/src/bitgen/version.py +4 -0
@@ -0,0 +1,8 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ dist/
5
+ *.egg-info/
6
+ .pytest_cache/
7
+ .mypy_cache/
8
+ .ruff_cache/
@@ -0,0 +1,23 @@
1
+ ## [1.0.0] - 2026-09-19
2
+
3
+ ### Added
4
+ - `BitgenClient` with keyword-only arguments (`scope`, `apiKey`, `env`, `host`, `port`, `isSsl`, `timeout`); `env` is one of the `bitgen.Env` constants
5
+ - `Env` (`PRODUCTION`, `SANDBOX`) and `Asset` (`BTC`, `ETH`, `USDC`, `XRP`, `SOL` — provisional list) string constants: every enumerated value is a constant on a frozen class listing its `VALUES`
6
+ - `BitgenError` with `status` (the HTTP status) and `code` (the stable error code of the API); `str(error)` is `"<code> (HTTP <status>)"`
7
+ - `request_timeout` / `network_error` errors (`status` `0`, transport error in `__cause__`)
8
+ - `timeout` option, in seconds (default `30`, `0` disables), bounding the whole request
9
+ - Invalid arguments raise a `ValueError` (or a `TypeError` on a wrong type) before any request
10
+ - `User-Agent: bitgen-sdk-python/<version>` header on every request
11
+ - `Page[T]` for paginated lists (`count`, `items`); `UnexpectedAnswerError` for a 2xx answer that is not the shape the API promises
12
+ - `customer` resource (`create` with the `needActivation` / `notify` options, `list`, `get`, `update`) and its models — `Identity` as `KycIdentity` / `KybIdentity`; `UserRef` (`str | Created | Customer | Account | UserSummary | OrderUser`) wherever a customer is expected
13
+ - `bank` resource (`get`, `operations`, `withdraw`, `credit`) and its models — amounts as `str`, `int`, `float` or `Decimal`
14
+ - `custody` resource (`wallets`, `wallet`, `portfolio`, `withdraw` with `TravelRulePerson` / `TravelRulePlatform`) and its models
15
+ - `trading` resource (`buy`, `sell`, `get`, `list`) and its models
16
+ - `transaction` resource (`list`, `get`) and its models
17
+ - `staking` resource (`providers`, `stake`, `list`, `movements`, `get`, `rewards`, `unstake`, `operations`, `portfolio`) and its models
18
+ - `core` resource (`list`, `get`) and its models
19
+ - `webhooks` resource (`activate`, `updateEndpoint`, `regenerate`, `list`, `subscribe`, `archive`, `reactivate`, `logs`, `catalog`, `catalogItem`) and its models, with `verify()` to check a received delivery (HMAC signature, freshness, envelope) — the headers as any `dict`, WSGI `environ`, framework `headers` object or list of `(name, value)` pairs
20
+ - `apikeys` resource (`list`, `get`, `logs`) and its models
21
+ - Every method that expects the uuid of an object the SDK returns also takes the model itself (`Order`, `StakingMovement`, `StakingPosition`, `Core`, `Transaction`, `Subscriber`, `WebhookType`, `Apikey`)
22
+ - `asset` resource (`list`, `get`, `tickers`, `ticker`) and its models under `bitgen.models` (`Asset`, `AssetTicker`, `AssetFees`, `AssetNetwork`…, the `AssetState` constants, the shared `History`); every method that expects an asset also takes an `Asset` / `AssetRef` model
23
+ - No dependency beyond the standard library; Python 3.11 or later; fully typed (`py.typed`)
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: bitgen-sdk
3
+ Version: 1.0.0
4
+ Summary: Official Python SDK for the BITGEN API v4
5
+ Project-URL: Homepage, https://github.com/bitgenplatform/sdk-python
6
+ Project-URL: Repository, https://github.com/bitgenplatform/sdk-python
7
+ Project-URL: Changelog, https://github.com/bitgenplatform/sdk-python/blob/main/CHANGELOG.md
8
+ Author: BITGEN
9
+ License-Expression: LicenseRef-Proprietary
10
+ Keywords: bitgen,crypto,sdk
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.11
22
+ Provides-Extra: dev
23
+ Requires-Dist: build<2,>=1.6; extra == 'dev'
24
+ Requires-Dist: mypy<1.21,>=1.20; extra == 'dev'
25
+ Requires-Dist: pytest<9,>=8.4; extra == 'dev'
26
+ Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
27
+ Requires-Dist: twine<8,>=7.0; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # bitgen-sdk — v1.0.0
31
+
32
+ Official Python SDK for the BITGEN API v4 — server-side, Python 3.11+, no dependency beyond the standard library.
33
+ Install it with `pip install bitgen-sdk`.
34
+
35
+ ```python
36
+ from bitgen import BitgenClient, Env
37
+
38
+ client = BitgenClient(
39
+ scope="YOUR_SCOPE_UUID", # uuid of the organization that owns the key
40
+ apiKey="YOUR_API_KEY",
41
+ env=Env.SANDBOX, # Env.PRODUCTION by default
42
+ )
43
+ ```
44
+
45
+ - [Installation](readme/installation.md) — Python 3.11+, pip, what to import
46
+ - [Quick start](readme/quick-start.md) — a customer, their EUR account, a wallet, a purchase
47
+ - [Configuration](readme/configuration.md) — credentials, environments, custom host, timeout
48
+ - [Concepts](readme/concepts.md) — user references, amounts, pagination, assets, activation, the flows of a purchase, a sale, a deposit and a withdrawal
49
+ - [Errors](readme/errors.md) — `BitgenError`, error codes, invalid arguments
50
+
51
+ Resources, in the order of an integration:
52
+
53
+ - [Customers](readme/resource/customer.md) — `client.customer`
54
+ - [Bank accounts](readme/resource/bank.md) — `client.bank`
55
+ - [Custody wallets](readme/resource/custody.md) — `client.custody`
56
+ - [Trading](readme/resource/trading.md) — `client.trading`
57
+ - [Transactions](readme/resource/transaction.md) — `client.transaction`
58
+ - [Staking](readme/resource/staking.md) — `client.staking`
59
+ - [Connectors](readme/resource/core.md) — `client.core`
60
+ - [Webhooks](readme/resource/webhooks.md) — `client.webhooks`
61
+ - [API keys](readme/resource/apikeys.md) — `client.apikeys`
62
+ - [Assets](readme/resource/asset.md) — `client.asset`
63
+
64
+ ## License
65
+
66
+ Private — © BITGEN
@@ -0,0 +1,37 @@
1
+ # bitgen-sdk — v1.0.0
2
+
3
+ Official Python SDK for the BITGEN API v4 — server-side, Python 3.11+, no dependency beyond the standard library.
4
+ Install it with `pip install bitgen-sdk`.
5
+
6
+ ```python
7
+ from bitgen import BitgenClient, Env
8
+
9
+ client = BitgenClient(
10
+ scope="YOUR_SCOPE_UUID", # uuid of the organization that owns the key
11
+ apiKey="YOUR_API_KEY",
12
+ env=Env.SANDBOX, # Env.PRODUCTION by default
13
+ )
14
+ ```
15
+
16
+ - [Installation](readme/installation.md) — Python 3.11+, pip, what to import
17
+ - [Quick start](readme/quick-start.md) — a customer, their EUR account, a wallet, a purchase
18
+ - [Configuration](readme/configuration.md) — credentials, environments, custom host, timeout
19
+ - [Concepts](readme/concepts.md) — user references, amounts, pagination, assets, activation, the flows of a purchase, a sale, a deposit and a withdrawal
20
+ - [Errors](readme/errors.md) — `BitgenError`, error codes, invalid arguments
21
+
22
+ Resources, in the order of an integration:
23
+
24
+ - [Customers](readme/resource/customer.md) — `client.customer`
25
+ - [Bank accounts](readme/resource/bank.md) — `client.bank`
26
+ - [Custody wallets](readme/resource/custody.md) — `client.custody`
27
+ - [Trading](readme/resource/trading.md) — `client.trading`
28
+ - [Transactions](readme/resource/transaction.md) — `client.transaction`
29
+ - [Staking](readme/resource/staking.md) — `client.staking`
30
+ - [Connectors](readme/resource/core.md) — `client.core`
31
+ - [Webhooks](readme/resource/webhooks.md) — `client.webhooks`
32
+ - [API keys](readme/resource/apikeys.md) — `client.apikeys`
33
+ - [Assets](readme/resource/asset.md) — `client.asset`
34
+
35
+ ## License
36
+
37
+ Private — © BITGEN
@@ -0,0 +1,74 @@
1
+ [build-system]
2
+ # hatchling 1.27 brings PEP 639 (the license expression); 1.32 starts emitting Metadata 2.5 — 2.4 until PyPI is known to take it
3
+ requires = ["hatchling>=1.27,<1.32"]
4
+ build-backend = "hatchling.build"
5
+
6
+ [project]
7
+ name = "bitgen-sdk"
8
+ dynamic = ["version"]
9
+ description = "Official Python SDK for the BITGEN API v4"
10
+ readme = "README.md"
11
+ requires-python = ">=3.11"
12
+ license = "LicenseRef-Proprietary"
13
+ authors = [{ name = "BITGEN" }]
14
+ keywords = ["bitgen", "crypto", "sdk"]
15
+ classifiers = [
16
+ "Development Status :: 5 - Production/Stable",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Typing :: Typed",
26
+ ]
27
+ dependencies = []
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/bitgenplatform/sdk-python"
31
+ Repository = "https://github.com/bitgenplatform/sdk-python"
32
+ Changelog = "https://github.com/bitgenplatform/sdk-python/blob/main/CHANGELOG.md"
33
+
34
+ [project.optional-dependencies]
35
+ dev = [
36
+ "pytest>=8.4,<9",
37
+ "mypy>=1.20,<1.21",
38
+ "ruff>=0.16,<0.17",
39
+ "build>=1.6,<2",
40
+ "twine>=7.0,<8",
41
+ ]
42
+
43
+ [tool.hatch.version]
44
+ path = "src/bitgen/version.py"
45
+
46
+ [tool.hatch.build.targets.sdist]
47
+ only-include = ["src/bitgen", "readme", "README.md", "CHANGELOG.md", "pyproject.toml"]
48
+
49
+ [tool.hatch.build.targets.wheel]
50
+ packages = ["src/bitgen"]
51
+
52
+ [tool.pytest.ini_options]
53
+ testpaths = ["tests"]
54
+ filterwarnings = ["error"]
55
+
56
+ [tool.mypy]
57
+ files = ["src", "tests"]
58
+ python_version = "3.11"
59
+ strict = true
60
+ warn_unreachable = true
61
+
62
+ [tool.ruff]
63
+ line-length = 120
64
+ target-version = "py311"
65
+ src = ["src", "tests"]
66
+
67
+ [tool.ruff.lint]
68
+ select = ["E", "F", "W", "I", "UP", "B", "SIM", "RUF", "PL", "PERF", "RET", "T20", "S"]
69
+ ignore = ["PLR2004", "PLR0913", "PLR0917"]
70
+
71
+ [tool.ruff.lint.per-file-ignores]
72
+ # tests: assert is the point, subprocess runs the doc examples and openssl with our own arguments, pickle our own
73
+ # errors, the webhook secrets are fixtures
74
+ "tests/**" = ["T20", "S101", "S603", "S301", "S105"]
@@ -0,0 +1,128 @@
1
+ # Concepts
2
+
3
+ The conventions shared by every resource of the SDK: how a customer is designated, how amounts, pages, booleans, assets and dates travel, and which customers the financial resources accept. Examples use `client`, a configured `BitgenClient` ([Configuration](configuration.md)), and `customer`, the `Created` returned by `client.customer.create()`.
4
+
5
+ ## User references
6
+
7
+ Wherever a method expects a customer, its signature says `UserRef` — a type alias of `bitgen.models`: `str | Created | Customer | Account | UserSummary | OrderUser`. A string is the customer's **uuid**; a model is one that carries it. The SDK sends the uuid of the model: the `Created` returned by `client.customer.create()`, a `Customer` of `client.customer.list()`, an `Account` of `client.customer.get()`, the `user` of an `Order`, the `owner` of a `Transaction` or of a `StakingMovement`.
8
+
9
+ ```python
10
+ account = client.bank.get("CUSTOMER_UUID")
11
+ account = client.bank.get(customer) # the Created returned by client.customer.create()
12
+ ```
13
+
14
+ Any other object — a `Wallet`, an `Order`, a dict with a `uuid` — is a `TypeError` at the call, before any request. An email works too where the API resolves it (`customer`, `bank`, `custody`, `staking` — not `trading`, nor the `user` filter of the lists), but the uuid is cheaper for the API — prefer it.
15
+
16
+ The same goes for the other objects the SDK returns: wherever a method expects the uuid of an order, a movement, a position, a connector, a transaction, a subscription, an event of the catalogue or a key, it also takes the model itself (`client.staking.rewards(movement.staking)`, `client.trading.get(order)`), and sends its uuid.
17
+
18
+ ## Amounts
19
+
20
+ The API handles crypto amounts as **strings** (up to 18 decimals): a Python `float` only keeps about 15 significant digits and nothing on the server side restores what it lost. The SDK therefore:
21
+
22
+ - accepts a `str`, an `int`, a `float` or a `Decimal` and always sends it as a string — a string is sent as is, an `int` or a `float` in its shortest decimal form, a `Decimal` in plain notation;
23
+ - refuses, with a `ValueError` and before any request, an empty string, a negative or non-finite number, and a `float` Python would write in exponent notation — below `0.0001` or from `1e16`: pass those as strings or as `Decimal`;
24
+ - never rounds or reformats a string: `"0.000000000000000001"` reaches the API untouched.
25
+
26
+ **Prefer strings or `Decimal`**, even for EUR: `0.1 + 0.2` is `0.30000000000000004` as a `float`. In responses, crypto quantities are strings and EUR amounts are `float`s with 2 decimals.
27
+
28
+ **Minimums.** Purchases, sales, on-chain withdrawals and staking movements have minimums — set by BITGEN per environment, subject to change, never hard-coded in the SDK. The API's answer is the source of truth: `416 invalid_amount` for a purchase or a sale, `416 withdraw_below_minimum` for an on-chain withdrawal, `422 amount_below_minimum` for a staking movement. For staking, the provider's minimums are readable in its configuration (`client.staking.providers()`, [Staking](resource/staking.md#providers)).
29
+
30
+ ## Pagination
31
+
32
+ Paginated lists take `offset` and `limit` (`limit`: default 10, max 50; the transaction journal accepts up to 100) and return a `bitgen.Page`:
33
+
34
+ ```python
35
+ page = client.asset.list()
36
+
37
+ page.count # the total number of items
38
+ page.items # the items of this page, typed models — Page[models.Asset] here
39
+ ```
40
+
41
+ `Page[T]` is generic: `page.items` is a `list[T]`, and a type checker knows what each item is.
42
+
43
+ ## Query booleans
44
+
45
+ The boolean filters of the lists (`includeClosed`, `includeRevoked`, `includeArchived`) are sent as `true` / `false`. The API also reads `1` / `0`, treats an absent parameter as `false`, and answers `422 invalid_<param>` for any other value: `invalid_include_closed`, `invalid_include_revoked`, `invalid_include_archived`.
46
+
47
+ ## Assets
48
+
49
+ Wherever an asset is expected, the SDK accepts its **uuid** or its **ISO code** as a string, in any case (the API normalizes it) — or an `Asset` / `AssetRef` model returned by the SDK, whose uuid is then sent. The `bitgen.Asset` constants are the ISO codes of the main assets; any other code known to the catalogue ([Assets](resource/asset.md)) is passed as a plain string.
50
+
51
+ ```python
52
+ from bitgen import Asset
53
+
54
+ Asset.BTC # "btc"
55
+ Asset.ETH # "eth"
56
+ Asset.USDC # "usdc"
57
+ Asset.XRP # "xrp"
58
+ Asset.SOL # "sol"
59
+ Asset.VALUES # ("btc", "eth", "usdc", "xrp", "sol")
60
+
61
+ eth = client.asset.get(Asset.ETH) # or by uuid
62
+ wallet = client.custody.wallet(customer, eth) # the model: its uuid is sent
63
+ ```
64
+
65
+ The `iso` the API returns has the case it is stored with (`ETH` today): **compare it case-insensitively**. `bitgen.Asset` holds the ISO codes; the asset returned by `client.asset` is the `bitgen.models.Asset` model.
66
+
67
+ ## Constants
68
+
69
+ The SDK has no enums: every value the API enumerates is a **string constant** on a small class — `Env.SANDBOX` is `"sandbox"`, `Locale.FR` is `"FR"`, `TradingDirection.BUY` is `"buy"`, `AssetState.AVAILABLE` is `"AVAILABLE"`, `WebhookEventName.CUSTODY_SENT` is `"custody.sent"` — and each class lists its values in `VALUES`, in the order of the API. The classes of the values a model carries live under `bitgen.models`, next to the model.
70
+
71
+ ```python
72
+ from bitgen import Asset
73
+ from bitgen.models import AssetState, Locale
74
+
75
+ eth = client.asset.get(Asset.ETH)
76
+ if eth.state == AssetState.AVAILABLE: # outputs are strings: compare them with the constants
77
+ client.customer.update(customer, locale=Locale.EN) # inputs take the constant
78
+ print(", ".join(Locale.VALUES)) # FR, EN
79
+ ```
80
+
81
+ An input that is not one of the values (`locale="en"`, `direction="Buy"`) is refused with a `ValueError` before any request: the case matters. An output the SDK does not know yet (a state the API added) is kept as is, as a string. The constants cannot be changed or instantiated.
82
+
83
+ ## Timestamps and histories
84
+
85
+ Timestamps are **epochs in seconds** (`createdAt`, `updatedAt`, `date`, `expiresAt`…). Time series are a `bitgen.models.History` with five lists of points, `d`, `w`, `m`, `y` and `all`:
86
+
87
+ ```python
88
+ from datetime import UTC, datetime
89
+
90
+ from bitgen import Asset
91
+
92
+ btc = client.asset.ticker(Asset.BTC)
93
+
94
+ for epoch, price in btc.history.d: # the last 24 hours, one point per hour
95
+ print(datetime.fromtimestamp(epoch, tz=UTC).strftime("%H:%M"), price)
96
+ ```
97
+
98
+ Each point is a tuple `(epoch seconds, value)`: `d` covers the last 24 hours with one point per hour, `w` and `m` one point per day, `y` and `all` one point per month; the last point is the current value. Histories are the EUR price of an asset (`client.asset`), the EUR balance of a bank account (`client.bank`), the EUR value of a wallet or of a whole custody (`client.custody`), and the capital and revenues of a staking portfolio (`client.staking`).
99
+
100
+ ## Activation and identity
101
+
102
+ By default, creating a customer sends them an activation email. Until they click it, the account stays `CREATED` and the **financial resources do not see it**: the bank answers `404 unknown_bank`, custody and staking `403 org_forbidden`, trading `403 user_not_in_scope`. Only the customer resource sees it (where the customer appears as `CREATED`). An organization that handles onboarding itself creates its customers with `needActivation=False` — usable right away, no BITGEN email — and `notify=False` for no BITGEN emails at all.
103
+
104
+ If your organization uses BITGEN's identity verification, the customer's identity (KYC for a person, KYB for a business) must be validated first — `412 owner_identity_not_validated` when reading the EUR account, `403 kyc_not_validated` on custody, trading and staking otherwise. An organization that verifies the identity of its customers by its own means has no such requirement. The verification itself is not part of the SDK; its state is the `state` of the customer's identity.
105
+
106
+ ![Activation and identity: from the creation of a customer to the financial resources](media/activation.svg)
107
+
108
+ ## Following a purchase and a sale
109
+
110
+ A purchase moves money across four resources. Placing the order reserves the EUR on the customer's bank account — an operation `PURCHASE` ([Bank accounts](resource/bank.md)); the exchange of the platform executes it — `EXECUTING`, then `FILLED` with the price, the fee and the quantity received ([Trading](resource/trading.md)); the quantity is delivered to the customer's custody wallet — `DELIVERING`, then `DONE` ([Custody wallets](resource/custody.md)) — where it appears as a custody transaction `IN` ([Transactions](resource/transaction.md)). Afterwards, the order carries the result, the operations of the bank account show the debit, the journal the delivery, the wallet its new balance, and the event `trading.buy` is sent ([Webhooks](resource/webhooks.md)).
111
+
112
+ ![A purchase: the EUR reserved, the execution at the exchange, the delivery to the custody wallet, and what you read afterwards](media/purchase-flow.svg)
113
+
114
+ A sale goes the other way. The quantity leaves the customer's custody wallet through an internal transfer to the exchange — a `silent` custody transaction `OUT` in the journal ([Custody wallets](resource/custody.md), [Transactions](resource/transaction.md)); the exchange executes it — `EXECUTING`, then `FILLED` at `executedPrice`, minus `fee` ([Trading](resource/trading.md)); the EUR received are credited on the customer's EUR account — `DONE`, an operation `SELL` and the event `bank.credited` ([Bank accounts](resource/bank.md)); the event `trading.sell` is sent ([Webhooks](resource/webhooks.md)).
115
+
116
+ ![A sale: the crypto moved from the custody wallet to the exchange, the execution, the EUR credited on the ledger, and what your organization reads afterwards](media/sale-flow.svg)
117
+
118
+ ## Following a deposit and a withdrawal
119
+
120
+ The EUR of a customer are held by the bank provider of your organization, on the organization's account — one IBAN, the same for all your customers, that you give them yourself. BITGEN holds no funds: it keeps the **ledger** of each customer — `balance`, `pending`, `history`, operations ([Bank accounts](resource/bank.md)). The provider receives the wires and pays the withdrawals; you read the ledger and receive the events ([Webhooks](resource/webhooks.md)).
121
+
122
+ ![An EUR deposit: the wire to the organization account at the bank provider, its report, the matching by reference, the compliance analysis, the credit of the ledger](media/deposit-flow.svg)
123
+
124
+ A deposit: the customer wires EUR to the organization's account with the reference `BTGN` followed by the code of their account (`message`) → the provider reports the wire to BITGEN — with a manual bank, you declare it ([credit](resource/bank.md#credit)) → BITGEN matches the reference and the amount enters `pending.in_` → compliance analysis: a `BANK` `IN` transaction, `bank.transaction` (`PENDING` if an alert holds it — [Transactions](resource/transaction.md)) → `balance` credited, operation `DEPOSIT`, `bank.credited`. The credit is never immediate; without a valid reference the wire is never credited, and the compliance of your organization is notified.
125
+
126
+ ![An EUR withdrawal: the reserve on the ledger, the compliance analysis, the wire from the organization account to the customer IBAN, the debit at confirmation](media/withdrawal-flow.svg)
127
+
128
+ A withdrawal: you request it ([Withdraw](resource/bank.md#withdraw)) — the amount is reserved in `pending.out`, `balance` untouched, `transaction` identifies the withdrawal → compliance analysis, `bank.transaction` → the provider wires from the organization's account to the customer's IBAN → at confirmation, `balance` debited, operation `WITHDRAWAL`, `bank.debited` with `amount`, `fee` and `net`. If it fails, the reserve is released.
@@ -0,0 +1,74 @@
1
+ # Configuration
2
+
3
+ A `BitgenClient` is built once per API key and reused — it can be shared between threads: it holds the credentials, the target environment and the request timeout, and nothing else. Its arguments are keyword-only.
4
+
5
+ ```python
6
+ from bitgen import BitgenClient, Env
7
+
8
+ client = BitgenClient(
9
+ scope="YOUR_SCOPE_UUID",
10
+ apiKey="YOUR_API_KEY",
11
+ env=Env.PRODUCTION, # default
12
+ timeout=30, # seconds, default 30
13
+ )
14
+ ```
15
+
16
+ ## Credentials
17
+
18
+ | Argument | Description |
19
+ |---|---|
20
+ | `scope` | uuid of the organization that owns the key. Sent as the `BITGEN-Scope` header. It is also the organization the SDK uses wherever the API expects yours. |
21
+ | `apiKey` | The raw key, shown once when it is created. Sent as the `Api-key` header. |
22
+
23
+ A missing key, an unknown, revoked or expired key, or a `scope` that is not the key's organization, is refused with a `401` ([Common errors](errors.md#common-errors)).
24
+
25
+ ## Environments
26
+
27
+ | `env` | Constant | URL |
28
+ |---|---|---|
29
+ | `production` | `Env.PRODUCTION` (default) | `https://api.bitgen.com` |
30
+ | `sandbox` | `Env.SANDBOX` | `https://api.sandbox.bitgen.com` |
31
+
32
+ `Env` holds these names as constants (`Env.VALUES` lists them): pass the constant. Anything else is refused before any request ([Validation](#validation)).
33
+
34
+ ## Custom host
35
+
36
+ To reach the API through another hostname — a container, a tunnel — give `host` instead of `env`:
37
+
38
+ ```python
39
+ from bitgen import BitgenClient
40
+
41
+ client = BitgenClient(
42
+ scope="YOUR_SCOPE_UUID",
43
+ apiKey="YOUR_API_KEY",
44
+ host="my-hostname", # bare hostname: no scheme, port or path
45
+ port=8080, # default 80
46
+ isSsl=False, # default True (https)
47
+ )
48
+ ```
49
+
50
+ `isSsl=False` sends the key unencrypted: only towards a local container or a tunnel, never across a network.
51
+
52
+ ## Timeout
53
+
54
+ `timeout` is the maximum time, in seconds, the SDK waits for the API to answer — the whole request, from the connection to the last byte of the answer: `30` by default, `0` disables it; an `int` or a `float` (`0.5`). When it expires, the call raises a `BitgenError` with `status` `0` and `code` `request_timeout` ([No HTTP response](errors.md#no-http-response)).
55
+
56
+ ## Requests
57
+
58
+ Every request carries the headers `BITGEN-Scope`, `Api-key`, `Content-Type: application/json`, `Accept: application/json` and `User-Agent: bitgen-sdk-python/<version>`, where `<version>` is the installed version of the SDK. TLS certificates are verified against the certificates of the system — or against those named by the `SSL_CERT_FILE` / `SSL_CERT_DIR` environment variables of OpenSSL, for a private certificate authority. Redirects are never followed. The SDK connects directly: it does not read the proxy variables of the environment.
59
+
60
+ ## Validation
61
+
62
+ An invalid configuration raises a `ValueError` from the constructor — or a `TypeError` when an argument has the wrong type — before any request is sent: empty `scope` or `apiKey` (or one that is not printable ASCII), `env` that is not one of `Env.VALUES`, `host` that is not a bare hostname, `port` outside 1–65535, `timeout` that is not a number of seconds between `0` and `2147483`. The values of `scope`, `apiKey`, `env` and `host` never appear in the message. Every other invalid argument is refused the same way, by the method that receives it ([Invalid arguments](errors.md#invalid-arguments)).
63
+
64
+ ## Options
65
+
66
+ | Argument | Type | Default | Description |
67
+ |---|---|---|---|
68
+ | `scope` | `str` | — | Organization uuid |
69
+ | `apiKey` | `str` | — | API key |
70
+ | `env` | `str` | `Env.PRODUCTION` | Target environment — an `Env` constant |
71
+ | `host` | `str \| None` | `None` | Custom hostname, used instead of `env` |
72
+ | `port` | `int \| None` | `80` | Port, with `host` |
73
+ | `isSsl` | `bool` | `True` | `https` or `http`, with `host` |
74
+ | `timeout` | `int \| float` | `30` | Request timeout in seconds, `0` = none |
@@ -0,0 +1,73 @@
1
+ # Errors
2
+
3
+ The API answers with real HTTP status codes and, on failure, a JSON body `{ error: true, message: '<code>', code: <status> }` where `message` is a stable snake_case code (`invalid_amount`, `unknown_asset`…), never a sentence. The SDK turns every non-2xx answer — and every request that gets no HTTP answer at all — into a `BitgenError`.
4
+
5
+ ## BitgenError
6
+
7
+ `bitgen.BitgenError` extends `Exception`:
8
+
9
+ | Member | Type | Description |
10
+ |---|---|---|
11
+ | `status` | `int` | The HTTP status (`416`…), or `0` when no HTTP response was received |
12
+ | `code` | `str` | The stable code of the API (`requested_amount_error`), or the raw response text when the body is not the API's JSON error |
13
+ | `str(error)` | `str` | `"<code> (HTTP <status>)"` |
14
+ | `__cause__` | `BaseException \| None` | The transport error, for `request_timeout` and `network_error` — `None` otherwise |
15
+
16
+ ```python
17
+ from bitgen import BitgenError
18
+
19
+ try:
20
+ ... # any call of the SDK
21
+ except BitgenError as error:
22
+ error.status # 416
23
+ error.code # "requested_amount_error"
24
+ str(error) # "requested_amount_error (HTTP 416)"
25
+ ```
26
+
27
+ Nothing in the exception ever contains your API key: not the message, not the cause — even a raw response that echoes it back has the key redacted.
28
+
29
+ ## No HTTP response
30
+
31
+ | Status | `code` | Meaning |
32
+ |---|---|---|
33
+ | `0` | `request_timeout` | No response within the configured `timeout` — 30 seconds by default ([Timeout](configuration.md#timeout)) |
34
+ | `0` | `network_error` | The request never got an HTTP answer: DNS, connection refused, TLS… `__cause__` holds the transport error |
35
+
36
+ ## Webhook verification
37
+
38
+ `client.webhooks.verify()` checks a delivery received by your endpoint locally, without any request: a delivery that fails the verification raises a `BitgenError` with `status` `0` and one of these codes ([Webhooks › verify](resource/webhooks.md#verify)):
39
+
40
+ | Status | `code` | Meaning |
41
+ |---|---|---|
42
+ | `0` | `missing_signature` | No `X-BITGEN-Signature` header |
43
+ | `0` | `invalid_signature` | The signature does not match the body and the secret |
44
+ | `0` | `missing_timestamp` | No `X-BITGEN-Timestamp` header, or not a number |
45
+ | `0` | `timestamp_expired` | The timestamp of the delivery is more than `tolerance` seconds away from now (300 by default) |
46
+ | `0` | `invalid_payload` | The body is not the JSON envelope `{ delivery_id, timestamp, event, data }` |
47
+
48
+ ## Unexpected answers
49
+
50
+ When the body is not the API's JSON error payload (proxy error page, unexpected `500`…), `code` holds the raw response text, truncated to 200 characters; the same goes for a 2xx answer that is not JSON. A `500` is not always a failure of the API: it is also its answer to a request it does not recognize — with a custom `host`, check that it reaches the API unchanged. Redirects are never followed: a `3xx` answer is reported as a `BitgenError`, and the key is never replayed to another host. A 2xx answer that is JSON but not the shape the SDK expects (not an object, not a list of objects where the API promises one, a page whose `items` is not a list…) raises a `bitgen.UnexpectedAnswerError`: not an error of the API, a contract violation to report to BITGEN. A value the SDK enumerates (a `state`, a `mode`…) is never checked: the API may add one, the models keep it as a string.
51
+
52
+ ## Invalid arguments
53
+
54
+ An invalid argument — an empty `scope` or `apiKey`, a `host` with a scheme, a negative `timeout`, a value outside a constant list ([Constants](concepts.md#constants)), an empty asset… — raises a `ValueError` **before any request is sent**, from the constructor or from the method that receives it; an argument of the wrong type (a `port` given as a string) raises a `TypeError` the same way ([Validation](configuration.md#validation)).
55
+
56
+ ## Common errors
57
+
58
+ The errors any call can answer.
59
+
60
+ | Status | `code` | Meaning |
61
+ |---|---|---|
62
+ | `401` | `auth_missing` | The `BITGEN-Scope` or `Api-key` header is missing |
63
+ | `401` | `api_key_missmatch` | The key is unknown, revoked or expired, or `scope` is not the key's organization |
64
+ | `403` | `unknown_organization` | The organization of the key is unknown |
65
+ | `403` | `organization_not_enabled` | The organization is not enabled |
66
+ | `403` | `api_disabled` | API access is not enabled for the organization |
67
+ | `403` | `invalid_api_key` | The key is invalid |
68
+ | `403` | `expired_api_key` | The key has expired |
69
+ | `403` | `forbidden_permission` | The key does not carry the permission for this call — contact BITGEN |
70
+ | `400` | `required_index_missing::<field>` | A mandatory field is missing or empty |
71
+ | `404` | `unknown_<resource>` | The target does not exist — `unknown_user`, `unknown_asset`, `unknown_bank`… — or the API does not reveal it |
72
+ | `422` | `invalid_<param>` | A boolean filter (`includeClosed`, `includeRevoked`, `includeArchived`) is not a boolean value |
73
+ | `423` | `blocked_by_alert` | The customer is under an active compliance alert |
@@ -0,0 +1,39 @@
1
+ # Installation
2
+
3
+ `bitgen-sdk` is the official Python SDK for the BITGEN API v4. It runs server-side only: the API accepts browser requests from a fixed list of origins, so the SDK is not meant to be used from a browser.
4
+
5
+ ## Requirements
6
+
7
+ - Python 3.11 or later
8
+ - No other dependency: the SDK only uses what Python already provides
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pip install bitgen-sdk
14
+ ```
15
+
16
+ ## Import
17
+
18
+ The client, its errors and the constants are imported from the `bitgen` package:
19
+
20
+ ```python
21
+ from bitgen import Asset, BitgenClient, BitgenError, Env
22
+ ```
23
+
24
+ The package exposes the client (`BitgenClient`), its exception (`BitgenError`), two constant classes — `Env`, the environments, and `Asset`, the ISO codes of the main assets ([Configuration](configuration.md), [Assets](concepts.md#assets)) — and `Page`, the paginated lists. Under `bitgen.models` live the models the resources return, the constant classes naming their known values ([Constants](concepts.md#constants)), and the few objects a call takes as input (`TravelRulePerson`, `TravelRulePlatform`, the `ReceivedHeaders` shape of `verify`):
25
+
26
+ ```python
27
+ from bitgen.models import AssetState, History
28
+ ```
29
+
30
+ ## Typing
31
+
32
+ The SDK is fully typed and ships its type information (`py.typed`), so a type checker such as mypy or pyright sees every signature: paginated lists are a generic `Page[T]` (`page.count`, `page.items`), and every value the API returns is an immutable object with typed attributes.
33
+
34
+ ## Next steps
35
+
36
+ - [Quick start](quick-start.md) — create the client and run a first customer journey
37
+ - [Configuration](configuration.md) — credentials, environments, custom host, timeout
38
+ - [Concepts](concepts.md) — user references, amounts, pagination, assets, activation, the flows of a purchase, a sale, a deposit and a withdrawal
39
+ - [Errors](errors.md) — what a failed call raises, and what is checked before any request
@@ -0,0 +1,58 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 560" role="img" aria-labelledby="title">
2
+ <title id="title">Activation and identity: from the creation of a customer to the financial resources</title>
3
+ <defs>
4
+ <marker id="m94a3b8" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="8" markerHeight="8" markerUnits="userSpaceOnUse" orient="auto"><path d="M0,0.5 L7,4 L0,7.5 z" fill="#94a3b8"/></marker>
5
+ <pattern id="grid" width="16" height="16" patternUnits="userSpaceOnUse"><circle cx="8" cy="8" r="1" fill="#e2e8f0"/></pattern>
6
+ <filter id="shadow" x="-10%" y="-20%" width="120%" height="160%"><feDropShadow dx="0" dy="1" stdDeviation="2" flood-color="#0f172a" flood-opacity="0.10"/></filter>
7
+ </defs>
8
+ <rect width="1000" height="560" fill="#ffffff"/>
9
+ <rect width="1000" height="560" fill="url(#grid)"/>
10
+ <text x="520.0" y="34.5" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="600" fill="#0f172a" text-anchor="middle">Activation and identity</text>
11
+ <rect x="420" y="54" width="200" height="40" rx="10" fill="#ffffff" stroke="#e2e8f0" filter="url(#shadow)"/>
12
+ <text x="520.0" y="78.7" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="500" fill="#0f172a" text-anchor="middle">Create a customer</text>
13
+ <path d="M520,95 V116" fill="none" stroke="#94a3b8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#m94a3b8)"/>
14
+ <rect x="455" y="118" width="130" height="24" rx="12.0" fill="#0f172a"/>
15
+ <text x="520.0" y="134.1" font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="11.5" font-weight="500" fill="#ffffff" text-anchor="middle">needActivation?</text>
16
+ <path d="M520,143 V178" fill="none" stroke="#94a3b8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#m94a3b8)"/>
17
+ <rect x="533" y="148" width="66" height="24" rx="12.0" fill="#f1f5f9"/>
18
+ <text x="566.0" y="164.1" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="500" fill="#475569" text-anchor="middle">default</text>
19
+ <rect x="360" y="180" width="320" height="56" rx="10" fill="#ffffff" stroke="#e2e8f0" filter="url(#shadow)"/>
20
+ <text x="520.0" y="204.2" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#0f172a" text-anchor="middle">Activation email sent, account <tspan font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="0.92em" font-weight="600" fill="#4338ca">CREATED</tspan></text>
21
+ <text x="520.0" y="221.2" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#0f172a" text-anchor="middle">until the customer clicks it</text>
22
+ <path d="M520,237 V282" fill="none" stroke="#94a3b8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#m94a3b8)"/>
23
+ <rect x="529" y="247" width="54" height="24" rx="12.0" fill="#f1f5f9"/>
24
+ <text x="556.0" y="263.1" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="500" fill="#475569" text-anchor="middle">click</text>
25
+ <path d="M590,130 H790 Q800,130 800,140 V306 Q800,316 790,316 H612" fill="none" stroke="#94a3b8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#m94a3b8)"/>
26
+ <rect x="713" y="118" width="54" height="24" rx="12.0" fill="#f1f5f9"/>
27
+ <text x="740.0" y="134.1" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="500" fill="#475569" text-anchor="middle">false</text>
28
+ <rect x="430" y="296" width="180" height="40" rx="10" fill="#ffffff" stroke="#e2e8f0" filter="url(#shadow)"/>
29
+ <text x="520.0" y="320.7" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#0f172a" text-anchor="middle">Account <tspan font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="0.92em" font-weight="600" fill="#4338ca">ENABLED</tspan></text>
30
+ <rect x="40" y="176" width="340" height="106" rx="10" fill="#ffffff" stroke="#e2e8f0" filter="url(#shadow)"/>
31
+ <path d="M50,188 V270" stroke="#dc2626" stroke-width="3" stroke-linecap="round"/>
32
+ <text x="64.0" y="200.0" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="600" fill="#0f172a" text-anchor="start">Meanwhile, only the customer resource sees it</text>
33
+ <text x="64.0" y="220.0" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="400" fill="#475569" text-anchor="start">bank: <tspan font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="0.92em" font-weight="600" fill="#dc2626">404 unknown_bank</tspan></text>
34
+ <text x="64.0" y="237.0" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="400" fill="#475569" text-anchor="start">custody, staking: <tspan font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="0.92em" font-weight="600" fill="#dc2626">403 org_forbidden</tspan></text>
35
+ <text x="64.0" y="254.0" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="400" fill="#475569" text-anchor="start">trading: <tspan font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="0.92em" font-weight="600" fill="#dc2626">403 user_not_in_scope</tspan></text>
36
+ <path d="M380,229 H358" fill="none" stroke="#cbd5e1" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="4 4"/>
37
+ <path d="M520,337 V358" fill="none" stroke="#94a3b8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#m94a3b8)"/>
38
+ <rect x="418" y="360" width="205" height="24" rx="12.0" fill="#0f172a"/>
39
+ <text x="520.0" y="376.1" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="500" fill="#ffffff" text-anchor="middle">BITGEN identity verification?</text>
40
+ <path d="M520,385 V420" fill="none" stroke="#94a3b8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#m94a3b8)"/>
41
+ <rect x="530" y="390" width="41" height="24" rx="12.0" fill="#f1f5f9"/>
42
+ <text x="550.0" y="406.1" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="500" fill="#475569" text-anchor="middle">yes</text>
43
+ <rect x="360" y="422" width="320" height="56" rx="10" fill="#ffffff" stroke="#e2e8f0" filter="url(#shadow)"/>
44
+ <text x="520.0" y="446.2" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#0f172a" text-anchor="middle">Identity verification, KYC or KYB,</text>
45
+ <text x="520.0" y="463.2" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#0f172a" text-anchor="middle">until the identity is <tspan font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="0.92em" font-weight="600" fill="#4338ca">VALIDATED</tspan></text>
46
+ <path d="M520,479 V510" fill="none" stroke="#94a3b8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#m94a3b8)"/>
47
+ <path d="M620,372 H790 Q800,372 800,382 V520 Q800,530 790,530 H672" fill="none" stroke="#94a3b8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#m94a3b8)"/>
48
+ <rect x="733" y="360" width="35" height="24" rx="12.0" fill="#f1f5f9"/>
49
+ <text x="750.0" y="376.1" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="500" fill="#475569" text-anchor="middle">no</text>
50
+ <rect x="40" y="412" width="340" height="88" rx="10" fill="#ffffff" stroke="#e2e8f0" filter="url(#shadow)"/>
51
+ <path d="M50,424 V488" stroke="#dc2626" stroke-width="3" stroke-linecap="round"/>
52
+ <text x="64.0" y="436.0" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="600" fill="#0f172a" text-anchor="start">Before that</text>
53
+ <text x="64.0" y="456.0" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="400" fill="#475569" text-anchor="start">EUR account: <tspan font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="0.92em" font-weight="600" fill="#dc2626">412 owner_identity_not_validated</tspan></text>
54
+ <text x="64.0" y="473.0" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="400" fill="#475569" text-anchor="start">custody, trading, staking: <tspan font-family="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="0.92em" font-weight="600" fill="#dc2626">403 kyc_not_validated</tspan></text>
55
+ <path d="M380,456 H358" fill="none" stroke="#cbd5e1" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="4 4"/>
56
+ <rect x="370" y="510" width="300" height="40" rx="10" fill="#dcfce7" stroke="#bbf7d0" filter="url(#shadow)"/>
57
+ <text x="520.0" y="534.7" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="600" fill="#15803d" text-anchor="middle">Bank account, custody, trading and staking</text>
58
+ </svg>