> ## Documentation Index
> Fetch the complete documentation index at: https://www.bitnob.dev/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Bitnob API requests authenticate with an `X-Auth-Client` API key header against https://api.bitnob.com.
> Always use the sandbox environment for testing. Never put live API keys in sample code.
> Money amounts are integers in the smallest unit of the currency (satoshis for BTC, cents for fiat). Never use floats.
> Every state-changing request takes a client-supplied `reference` and must be treated as idempotent on retry.
> Quotes expire. Re-quote rather than reusing a stale quote id.

# Get risk profile

> Returns a single KYT risk profile by ID, scoped to the authenticated organization.



## OpenAPI

````yaml /api-collections/swagger/bitnob-platform.openapi.json get /kyt/risk-profiles/{id}
openapi: 3.0.3
info:
  description: >-
    # Bitnob Enterprise API


    A high-performance Wallet-as-a-Service API for partners and developers:

    non-custodial wallets, transactions, gas stations, on-chain subscriptions
    and

    platform webhooks across multiple chains.


    ## Authentication


    Authenticate every request with your secret API key in the **X-API-Key**

    request header. Keys are scoped to one organization and carry a fixed set of

    permissions; an endpoint returns **403** when the key lacks the required

    permission. Treat the key as a credential — never expose it in a browser or

    mobile client.


    ## Request tracing


    Every response carries an **X-Request-Id** header. Log it, and quote it when

    contacting support — it identifies the exact request in our systems.


    ## Rate limits


    Wallet/business API and node RPC traffic have **independent rate-limit
    budgets**,

    even when they use the same API key and organization:


    | Traffic | Limit |

    |---|---|

    | Wallet and other non-RPC API requests | A per-key requests-per-minute
    allowance plus a configured burst, and a shared organization per-minute
    allowance |

    | **POST /rpc/{chain_network}**, including the **/v1/rpc/{chain_network}**
    alias | A shared organization requests-per-second limit, counted per
    JSON-RPC call |


    Authenticated RPC requests do not consume your wallet API quota, and wallet

    requests do not consume your RPC limit. The RPC limit is shared across all
    keys

    and chains in your organization; creating another key does not increase it.


    A JSON-RPC batch counts each call in the batch, not as a single request —
    batching

    many calls into one HTTP request consumes the limit per call. On live there
    is

    currently no additional per-day volume cap; sandbox applies a low per-second
    and

    per-day default so it cannot be used as production infrastructure. An RPC
    429

    carries **Retry-After**.


    Rate-limited responses expose the current state:


    - **X-RateLimit-Limit** — requests permitted in the current window.

    - **X-RateLimit-Remaining** — requests still available in this window.

    - **X-RateLimit-Reset** — Unix time (seconds) when the window resets.

    - **X-RateLimit-Scope** — **key** for authenticated non-RPC traffic, or
    **ip**
      for unauthenticated/failed-auth traffic. (RPC uses a separate per-second limit
      and returns only Retry-After on 429.)

    For non-RPC traffic, **X-Org-RateLimit-Limit**,
    **X-Org-RateLimit-Remaining**,

    and **X-Org-RateLimit-Reset** describe your organization-wide allowance
    shared

    across keys; the unprefixed headers report your per-key allowance or the

    organization allowance, whichever has less remaining. RPC proxy traffic is
    limited

    separately and returns only **Retry-After** on 429 (no X-RateLimit headers).


    Read these proactively and slow down before you are throttled. When a limit
    is

    exceeded the API responds **429** with a **Retry-After** header (seconds to

    wait); retry only after that delay.


    These are request-quota boundaries, not dedicated infrastructure: CPU,
    database

    and upstream provider capacity may still be shared. Authentication,
    permissions,

    IP restrictions, signatures and nonce replay protection are unchanged.


    ## Idempotency


    Send an **Idempotency-Key** header (a unique value you generate, e.g. a
    UUID)

    on **POST**, **PATCH** and **DELETE** requests so a network retry never

    applies the same change twice.


    - It is **required** on value-moving endpoints (creating transactions,
      creating addresses, refilling and sponsor-broadcasting gas stations,
      creating wallets); those return **400 idempotency_key_required** if absent.
    - Keys are retained for **24 hours**.

    - Retrying with the same key replays the original response byte-for-byte and
      adds an **Idempotent-Replayed: true** header.
    - Reusing a key with a **different request body** is rejected with
      **409 idempotency_conflict** — generate a fresh key per distinct operation.
    - Only successful (2xx) responses are stored, so a request that failed can
    be
      safely retried with the same key.
    - An Idempotency-Key sent without authentication is rejected with **401**.


    ## Pagination


    List endpoints are cursor-paginated. Parameters:


    - **limit** — page size; bounded by the endpoint maximum.

    - **starting_after** — return the page after this opaque cursor.

    - **ending_before** — return the page before this opaque cursor.


    Supplying both **starting_after** and **ending_before** is rejected with

    **400 ambiguous_cursor**. Every list response is the same envelope:

        {
          "object": "list",
          "data": [ ... ],
          "has_more": true,
          "next_cursor": "b3JnOjE2ODk...",
          "url": "/wallets"
        }

    When **has_more** is true, pass **next_cursor** as **starting_after** to
    fetch

    the following page. Cursors are opaque — do not parse or construct them.


    ## Transaction lifecycle and async outcomes


    Creating a transaction (POST /v1/transactions) returns one of two outcomes:


    - **201 Created** — signed and submitted for broadcast.

    - **202 Accepted** — accepted but not yet executing. The body's **status**
    says why:
      - **pending_authorization** — organization policy requires approval; the
        transaction executes once approvers approve it.
      - **delayed** — a policy time-lock holds the transaction until **execute_at**
        (an ISO-8601 timestamp on the response), when it executes automatically.

      In both cases the transaction is durably persisted and its funds are locked.

    A transaction's **status** moves through these states:


    - **Non-terminal**: pending_signature, pending_authorization, delayed,
    signed,
      awaiting_sponsorship, broadcasting, confirming, pending_screening.
    - **Terminal**: completed, failed, cancelled, rejected.


    Poll **GET /v1/transactions/{id}** (or subscribe to a webhook) until the
    status

    is terminal. An empty **tx_hash** means the transaction has not been
    broadcast

    on-chain yet; once broadcast, **confirmations** climbs until the transaction
    is

    **completed**.


    ## Amounts and units


    All monetary values are JSON **strings**, never numbers — parse them with a

    decimal/big-number type, not a float, to avoid precision loss.


    Every monetary value appears in two forms so you never have to guess
    decimals:


    - **amount** / **fee_amount** — human-readable decimal in the asset's
    display
      unit (e.g. "25.5" USDC).
    - **amount_raw** / **fee_amount_raw** / wallet **balance** — the integer
    value
      in the asset's smallest base unit (e.g. "25500000" for 25.5 USDC at 6
      decimals). **balance** fields are ALWAYS base units.

    **asset_decimals** (and **fee_decimals**) give the conversion:

    display = raw / 10^decimals. The fee is denominated in **fee_asset**, which
    for

    most chains is the native coin (ETH, TRX, …) and differs from the
    transferred

    asset — always read **fee_asset**/**fee_decimals** rather than assuming.
    After a

    transaction confirms, **realized_fee_amount**(_raw) carries the actual fee
    paid.


    Two balance shapes:


    - **GET /v1/wallets/{id}/balances** — one row PER FUNDED ADDRESS (an HD
    wallet
      has many addresses).
    - **GET /v1/wallets/{id}/balances/{asset}** — a single AGGREGATED total for
    that
      asset across the wallet.

    ## Fees


    Control the fee in one of two ways on transaction create and estimate:


    - **fee_level** — "low", "medium", or "high"; the platform picks a rate.

    - **fee_rate** / **max_fee_rate** — Bitcoin only, in sat/vB, for explicit
      control with an optional cap (the transaction will not pay above
      **max_fee_rate**).

    Use **POST /v1/transactions/estimate-fee** to preview low/medium/high quotes
    and

    any gas-funding options before sending.


    ## Errors


    Every error response is the same envelope:

        {
          "error": {
            "type": "invalid_request_error",
            "code": "invalid_cursor",
            "message": "starting_after is not a valid cursor",
            "param": "starting_after",
            "request_id": "req_01HX..."
          }
        }

    **type** is the broad category; **code** is a stable, machine-readable

    identifier — branch on **code**, show **message** to humans, and **param**

    names the offending field when applicable. Some errors also carry a
    **details**

    object with structured, error-specific context (for example the list of

    conflicting fields); treat it as optional and error-specific.


    ### Error code catalogue


    **invalid_request_error** (HTTP 400) — the request was malformed or failed

    validation: invalid_request, invalid_json, invalid_body, empty_body,

    trailing_data, type_mismatch, unknown_field, missing_required_field,

    invalid_id, invalid_cursor, ambiguous_cursor, invalid_expand, invalid_chain,

    invalid_vault_id, wallet_chain_unknown, invalid_transaction_data,

    amount_precision_loss, gas_station_low_balance, body_too_large,

    idempotency_key_required, idempotency_key_invalid, invalid_webhook_id,

    method_not_allowed.


    **authentication_error** (HTTP 401) — the request was not authenticated:

    not_authenticated, auth_required, org_required.


    **permission_error** (HTTP 403) — the request is authenticated but not

    permitted: insufficient_permissions (the API key lacks the required

    permission), ip_not_allowed (the request's source IP is not in the key's IP

    allowlist).


    **not_found_error** (HTTP 404) — the addressed resource does not exist:

    not_found, route_not_found, asset_not_found, event_not_found,

    gas_station_not_found, subscription_not_found, transaction_not_found,

    vault_not_found, wallet_not_found, address_not_found, webhook_not_found.


    **conflict_error** (HTTP 409) — the request conflicts with current state:

    conflict, idempotency_conflict, wallet_archived.


    **precondition_error** (HTTP 412) — a precondition on the request was not
    met,

    for example an ETag mismatch on a conditional request.


    **unprocessable_entity_error** (HTTP 422) — the request was well-formed but
    a

    business rule prevented it from being processed.


    **rate_limit_error** (HTTP 429) — too many requests: rate_limit_exceeded.


    **api_error** (HTTP 500) — an unexpected server error: request_error,

    internal_error.


    **service_unavailable_error** (HTTP 503) — a dependency is unavailable;

    retry after the Retry-After delay: service_unavailable.


    ## Webhook signature verification


    The platform signs every webhook delivery with HMAC-SHA256 using the signing

    secret issued when you create the webhook or subscription (and rotated via
    the

    rotate-secret endpoints — the plaintext secret is shown only once).


    The signature covers the canonical JSON object below, where **data**,

    **event** and **timestamp** are the corresponding fields of the delivered

    payload and **timestamp** is Unix seconds:

        {"data": <data>, "event": "<event>", "timestamp": <unix_seconds>}

    The signed object contains ONLY those three keys — when verifying, build a

    fresh object from the delivery's data, event and timestamp and exclude the

    signature field (and any other body fields) from the HMAC input. Serialize
    the

    keys in lexical order (data, event, timestamp), matching Go's encoding/json.

    The signature value is:

        "sha256=" + hex( HMAC_SHA256(signing_secret, canonical_json) )

    **Platform webhooks** (created via /webhooks) carry this value in the

    **X-Webhook-Signature** response header. **On-chain subscription webhooks**

    (created via /subscriptions) carry it as a **signature** field inside the

    delivered JSON body — there is no signature header.


    To verify a delivery: recompute the signature from your stored secret and

    compare it to the received value with a constant-time comparison. Reject the

    delivery on mismatch, and reject deliveries whose **timestamp** is older
    than

    your tolerance window (a few minutes is typical) to defend against replay.


    ## Webhook event types


    **Platform notification webhooks** (/webhooks) deliver these event types:

    wallet.created, wallet.deleted, wallet.suspended, wallet_address.created,

    transaction.created, transaction.signed, transaction.broadcasting,

    transaction.confirmed, transaction.completed, transaction.received,

    transaction.failed, transaction.cancelled, gas_station.low_balance,

    gas_station.critical_balance, gas_station.auto_refilled, session.created,

    api_key.created, api_key.deleted, policy.violation,

    policy.created, policy.updated, policy.activated, policy.archived,

    policy.deleted, policy.rule.added, policy.rule.updated, policy.rule.deleted,

    kyt.deposit.held, kyt.deposit.flagged, kyt.deposit.approved,
    kyt.deposit.rejected, kyt.deposit.blocked,

    kyt.withdrawal.blocked, kyt.withdrawal.flagged, kyt.sanctions.match,
    kyt.alert.escalated,

    kyt.screening.timeout, kyt.screening.requeued, kyt.screening.completed.


    Use **GET /v1/webhooks/event-types** for the authoritative machine-readable
    list.


    **On-chain subscription webhooks** (/subscriptions) deliver these event

    types: incoming_native_tx, incoming_token_tx, incoming_nft_transfer,

    incoming_internal_tx, outgoing_native_tx, outgoing_token_tx,

    outgoing_nft_transfer, outgoing_internal_tx, tx_failed, tx_pending,

    confirmation_threshold_met, confirmation_finalized, contract_deployed,

    contract_executed, balance_changed.


    ## Versioning and deprecation


    Pin the API version with the **API-Version** request header; omit it to take

    the current default. Additive changes (new endpoints, new optional fields)
    are

    not versioned and may appear at any time — write tolerant clients.


    When an operation or version is scheduled for removal it is marked

    **deprecated** in this specification, and its responses carry a

    **Deprecation** header plus a **Sunset** header (RFC 8594) giving the date

    after which it stops functioning. Monitor those headers and migrate before
    the

    Sunset date.
  title: Bitnob Enterprise API
  version: v1
servers:
  - description: Production
    url: https://api.platform.bitnob.com
security:
  - ApiKeyAuth: []
paths:
  /kyt/risk-profiles/{id}:
    get:
      tags:
        - Kyt/risk-profiles
      summary: Get risk profile
      description: >-
        Returns a single KYT risk profile by ID, scoped to the authenticated
        organization.
      operationId: getKytRisk-profilesById
      parameters:
        - description: Risk-profile ID
          in: path
          name: id
          required: true
          schema:
            format: uuid
            type: string
        - description: API version (defaults to the latest supported version if omitted).
          in: header
          name: API-Version
          required: false
          schema:
            default: v1
            enum:
              - v1
            type: string
      responses:
        '200':
          content:
            application/json:
              example:
                catchall:
                  high: block
                  low: allow
                  medium: hold
                  none: allow
                  severe: block
                created_at: '2026-05-16T10:30:00Z'
                id: f47ac10b-58cc-4372-a567-0e02b2c3d479
                is_default: true
                name: default
                object: risk_profile
                rules:
                  - categories: '["sanctions"]'
                    direction: outgoing
                    exposure_type: direct
                    level_actions:
                      high: block
                      none: allow
                      severe: block
                    max_exposure_usd: '5000'
                    min_exposure_usd: '1000'
                updated_at: '2026-05-16T10:30:00Z'
              schema:
                $ref: '#/components/schemas/RiskProfileResponse'
          description: Success
          headers:
            X-RateLimit-Limit:
              description: Requests permitted in the current window.
              schema:
                type: integer
            X-RateLimit-Remaining:
              description: Requests remaining in the current window.
              schema:
                type: integer
            X-RateLimit-Reset:
              description: Unix time (seconds) at which the rate-limit window resets.
              schema:
                type: integer
            X-RateLimit-Scope:
              description: >-
                Scope the rate limit was applied at: key for authenticated API
                traffic, or ip for unauthenticated/failed-auth traffic. RPC
                proxy traffic is limited separately (per second, per
                organization) and is not bucketed here.
              schema:
                type: string
            X-Request-Id:
              description: Unique id for this request; quote it in support requests.
              schema:
                type: string
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Invalid request
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Authentication required
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Insufficient permissions
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Not found
        '429':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Rate limit exceeded
          headers:
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: integer
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Internal server error
      security:
        - ApiKeyAuth: []
components:
  schemas:
    RiskProfileResponse:
      properties:
        catchall:
          $ref: '#/components/schemas/CatchAll'
          description: >-
            Baseline decision for every alert level, applied to any alert not
            matched by a rule.
        created_at:
          description: When the risk profile was created.
          example: '2026-05-16T10:30:00Z'
          format: date-time
          type: string
        id:
          description: Unique identifier of the risk profile.
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
          type: string
        is_default:
          description: >-
            Whether this profile is the organization's active default, applied
            to screening decisions.
          example: true
          type: boolean
        name:
          description: Human-readable name of the risk profile.
          example: default
          type: string
        object:
          description: Object type discriminator; always "risk_profile".
          example: risk_profile
          type: string
        rules:
          description: Category rules evaluated before the catch-all.
          items:
            $ref: '#/components/schemas/RiskProfileRule'
          type: array
        updated_at:
          description: When the risk profile was last updated.
          example: '2026-05-16T10:30:00Z'
          format: date-time
          type: string
      type: object
    Error:
      description: Standard error envelope.
      properties:
        error:
          properties:
            code:
              type: string
            details:
              additionalProperties: true
              type: object
            message:
              type: string
            param:
              type: string
            request_id:
              type: string
            type:
              type: string
          type: object
      type: object
    CatchAll:
      properties:
        high:
          description: Action for high-level alerts.
          enum:
            - allow
            - hold
            - block
            - flag
          example: block
          type: string
        low:
          description: Action for low-level alerts.
          enum:
            - allow
            - hold
            - block
            - flag
          example: allow
          type: string
        medium:
          description: Action for medium-level alerts.
          enum:
            - allow
            - hold
            - block
            - flag
          example: hold
          type: string
        none:
          description: 'Action for none-level alerts: allow, hold, block, or flag.'
          enum:
            - allow
            - hold
            - block
            - flag
          example: allow
          type: string
        severe:
          description: Action for severe-level alerts.
          enum:
            - allow
            - hold
            - block
            - flag
          example: block
          type: string
      required:
        - none
        - low
        - medium
        - high
        - severe
      type: object
    RiskProfileRule:
      properties:
        categories:
          description: >-
            Risk categories this rule matches, from the KYT category catalogue
            (see GET /kyt/categories).
          example: '["sanctions"]'
          items:
            enum:
              - sanctions
              - sanctioned_jurisdiction
              - eu_sanctions
              - terrorist_financing
              - child_abuse_material
              - darknet_market
              - stolen_funds
              - ransomware
              - mixing
              - illicit_actor
            type: string
          minItems: 1
          type: array
        direction:
          description: >-
            Restricts the rule to incoming or outgoing transfers. Omit, or use
            "any", for no direction constraint.
          enum:
            - any
            - incoming
            - outgoing
          example: outgoing
          type: string
        exposure_type:
          description: >-
            Restricts the rule to direct or indirect exposure. Omit, or use
            "any", for no exposure constraint.
          enum:
            - direct
            - indirect
            - any
          example: direct
          type: string
        level_actions:
          $ref: '#/components/schemas/LevelActions'
          description: >-
            Per-level action override for alerts this rule matches. At least one
            level must be set; any level left unset falls through to the profile
            catch-all.
        max_exposure_usd:
          description: >-
            Inclusive upper bound (USD) on alert exposure for the rule to match,
            as a decimal string; an alert at exactly this amount matches. Omit
            for no maximum.
          example: '5000'
          type: string
        min_exposure_usd:
          description: >-
            Inclusive lower bound (USD) on alert exposure for the rule to match,
            as a decimal string; an alert at exactly this amount matches. Omit
            for no minimum.
          example: '1000'
          type: string
      required:
        - categories
      type: object
    LevelActions:
      properties:
        high:
          description: Action for high-level alerts matched by this rule.
          enum:
            - allow
            - hold
            - block
            - flag
          example: block
          type: string
        low:
          description: Action for low-level alerts matched by this rule.
          enum:
            - allow
            - hold
            - block
            - flag
          type: string
        medium:
          description: Action for medium-level alerts matched by this rule.
          enum:
            - allow
            - hold
            - block
            - flag
          type: string
        none:
          description: Action for none-level alerts matched by this rule.
          enum:
            - allow
            - hold
            - block
            - flag
          example: allow
          type: string
        severe:
          description: Action for severe-level alerts matched by this rule.
          enum:
            - allow
            - hold
            - block
            - flag
          example: block
          type: string
      type: object
  securitySchemes:
    ApiKeyAuth:
      in: header
      name: X-API-Key
      type: apiKey

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.