> ## 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.

# Delete subscription

> Soft-deletes a subscription and removes it from the provider.



## OpenAPI

````yaml /api-collections/swagger/bitnob-platform.openapi.json delete /subscriptions/{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:
  /subscriptions/{id}:
    delete:
      tags:
        - Subscriptions
      summary: Delete subscription
      description: Soft-deletes a subscription and removes it from the provider.
      operationId: deleteSubscriptionsById
      parameters:
        - description: Subscription 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
        - description: >-
            Unique key (1-255 printable ASCII chars) used to safely retry write
            operations. Same key + body returns the cached response; same key +
            different body returns 409.
          in: header
          name: Idempotency-Key
          required: false
          schema:
            maxLength: 255
            minLength: 1
            type: string
      responses:
        '204':
          description: No Content
          headers:
            Idempotent-Replayed:
              description: >-
                True when this response was replayed from an earlier request
                with the same Idempotency-Key.
              schema:
                type: boolean
            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
        '409':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Conflict
        '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:
    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
  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.