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

# Create card deposit session

> **Auth context:** sub-user — `On-Behalf-Of` required. **Rate limit: 50/min.**

Returns a `whitelabelUrl` to redirect the sub-user to. After payment,
the on-ramp partner posts a callback and the sub-user's wallet is
credited; subscribe to `deposit.completed` WS events to react.




## OpenAPI

````yaml /openapi.yaml post /user/wallet/deposit/onramp/session
openapi: 3.1.0
info:
  title: SkinShark Merchant API
  description: >
    REST + WebSocket surface that you call **server-to-server** with your API
    key.

    Use it to manage sub-users, query wallets and trades, and act on behalf of
    any

    of your sub-users from a single key.


    ## Authentication


    Send your API key on every request:


    ```

    api-key: <your-key>

    ```


    Keys are hashed at rest and may be restricted to specific source IPs.
    Requests

    from a non-allowlisted IP are rejected with `API_KEY_IP_DENIED`.


    ### Acting on behalf of a sub-user


    For routes **outside** `/merchant/...`, add a header to scope the call to a

    specific sub-user:


    ```

    On-Behalf-Of: <subUserId | externalId>

    ```


    Both the UUID and the `externalId` you assigned are accepted; the server

    resolves either, scoped to your merchant. A UUID that belongs to a different

    merchant returns `USER_NOT_FOUND`.


    Without `On-Behalf-Of`, calls run **as your merchant account itself**. For

    example, `POST /market/buy` without the header buys for the merchant — its

    own spot wallet, its own Steam trade URL.


    ### Route groups


    | Prefix | What it does | `On-Behalf-Of` |

    |---|---|---|

    | `/merchant/...` | Merchant-level reads/writes (sub-users, trades
    aggregation, wallet, profile, fees). | Rejected — sub-users do not hold the
    merchant role. |

    | Everything else (`/user/...`, `/market/...`, `/auth/ws-token`) |
    User-scope operations. | Accepted — call runs as the targeted sub-user. |


    Each operation states the required context under **Auth context**.


    ## Dashboard-only operations


    A few account-management operations are not exposed to API key auth — they

    require signing in to the merchant dashboard:


    - Creating, rotating, and revoking API keys

    - Configuring webhook endpoints, secrets, and inspecting deliveries

    - Adjusting merchant fees

    - Account settings (password, email, 2FA, account deletion)

    - Audit logs and CSV exports


    Everything else in this document is available via API key.


    ## Response envelope


    Every JSON response is wrapped. Successful responses:


    ```json

    {
      "requestId": "req-...",
      "success": true,
      "data": { }
    }

    ```


    Error responses:


    ```json

    {
      "requestId": "req-...",
      "success": false,
      "error": {
        "code": 1500,
        "key": "INSUFFICIENT_BALANCE",
        "message": "Insufficient balance"
      }
    }

    ```


    Schemas below describe only the inner `data` shape — the envelope is
    implicit.


    ## Money format


    - Fields suffixed `Cents` are integer cents as JSON numbers
      (`15000` = $150.00).
    - Decimal-currency fields like `balance`, `totalPrice`, `gmv` are JSON
    numbers
      in the wallet currency (`5.23` = $5.23 in a USD wallet).
    - Amount **inputs** (e.g. `fund`, deposit `amount`) accept decimal strings
      (`"5.50"`) to avoid floating-point loss on the wire.

    ## Pagination


    Two styles are used, documented per endpoint:


    - **Page-based** — `?page=1&limit=25`. Response contains
      `{ items|users, total, page, limit, totalPages }`.
    - **Cursor-based** — `?cursor=<id>&limit=25`. Response contains
      `{ items|trades|transactions, nextCursor }`. `nextCursor` is `null` on the
      last page.

    ## Idempotency


    `POST /merchant/users/{id}/fund` requires an `Idempotency-Key` header.
    Replays

    with the same key return `idempotent: true` alongside the original

    transaction id, so retries are safe.


    ## Sub-user identifiers


    Anywhere a sub-user is referenced — the `On-Behalf-Of` header, `{id}` path

    params under `/merchant/users/...`, the `subUserId` filter on trade lists —

    the value can be **either**:


    - the sub-user's UUID, or

    - the `externalId` you assigned.


    The server resolves both, scoped to your merchant.
  version: 0.4.3
  contact:
    name: SkinShark Engineering
    email: support@skinshark.gg
  license:
    name: Proprietary
    url: https://skinshark.gg/terms
servers:
  - url: https://api.skinshark.gg
    description: Production
  - url: https://api-staging.skinshark.gg
    description: Staging
security:
  - apiKeyAuth: []
tags:
  - name: Account
    description: Merchant identity, fees, stats, wallet, ledger. Merchant context only.
  - name: Users
    description: >-
      Sub-user CRUD plus per-user wallet, ledger, trades, and funding. Merchant
      context only.
  - name: Trades
    description: Aggregate trade list across all sub-users. Merchant context only.
  - name: Profile
    description: >-
      Read the actor's profile (Steam link, Discord link, wallet snapshot).
      Merchant or sub-user context.
  - name: Trade URLs
    description: Manage Steam trade URLs for the actor. Merchant or sub-user context.
  - name: Wallet
    description: Actor's spot balance and ledger. Merchant or sub-user context.
  - name: Deposits
    description: >-
      Fund the actor's wallet via Gate Pay, on-ramp (card), or self-hosted EVM
      crypto. Merchant or sub-user context.
  - name: Partner Payout Custody
    description: >
      Separate per-merchant USD-denominated crypto custody (USDT/USDC at MVP).
      Funds deposited here

      are **not** spendable in the spot/skin-buying pipeline; they're
      withdrawable on-chain to any

      partner-supplied destination. All withdrawals require a synchronous
      approval callback to the

      merchant's registered `CallbackUrl` and a partner-supplied `externalId`
      for idempotency.

      Toggleable per merchant (`cryptoPayoutEnabled`).
  - name: Market
    description: >-
      Catalog search, listings, buy / quick-buy, and the actor's own trades.
      Merchant or sub-user context.
  - name: WebSocket
    description: >-
      Real-time trade and deposit events for a single sub-user
      (consumer-facing). Sub-user context only.
paths:
  /user/wallet/deposit/onramp/session:
    post:
      tags:
        - Deposits
      summary: Create card deposit session
      description: >
        **Auth context:** sub-user — `On-Behalf-Of` required. **Rate limit:
        50/min.**


        Returns a `whitelabelUrl` to redirect the sub-user to. After payment,

        the on-ramp partner posts a callback and the sub-user's wallet is

        credited; subscribe to `deposit.completed` WS events to react.
      operationId: createOnrampSession
      parameters:
        - $ref: '#/components/parameters/OnBehalfOf'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OnrampSessionBody'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/OnrampSessionResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
components:
  parameters:
    OnBehalfOf:
      name: On-Behalf-Of
      in: header
      description: |
        Sub-user UUID **or** the merchant's `externalId` for that sub-user.
        Required for sub-user-context routes; rejected (via `requireMerchant`)
        on merchant-context routes.
      required: false
      schema:
        type: string
        minLength: 1
        maxLength: 128
  schemas:
    OnrampSessionBody:
      allOf:
        - $ref: '#/components/schemas/OnrampQuoteBody'
        - type: object
          properties:
            redirectUrl:
              type: string
              format: uri
    Envelope:
      type: object
      required:
        - requestId
        - success
      properties:
        requestId:
          type: string
        success:
          type: boolean
        data:
          description: 'Present when `success: true`. Shape varies per endpoint.'
        error:
          $ref: '#/components/schemas/Error'
    OnrampSessionResponse:
      type: object
      required:
        - fundingId
        - status
        - currency
        - payAmount
        - receiveAmount
        - receiveAmountUsd
        - payCurrency
        - exchangeRate
        - whitelabelUrl
      properties:
        fundingId:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - initiated
            - completed
            - failed
        currency:
          $ref: '#/components/schemas/Currency'
        payAmount:
          type: number
        receiveAmount:
          type: number
        receiveAmountUsd:
          type: number
        payCurrency:
          $ref: '#/components/schemas/OnrampPayCurrency'
        exchangeRate:
          type: number
        whitelabelUrl:
          type: string
          format: uri
          description: Redirect the sub-user here to complete card payment.
    OnrampQuoteBody:
      type: object
      required:
        - payCurrency
      additionalProperties: false
      description: Provide either `payAmount` or `receiveAmount`.
      properties:
        payAmount:
          type: number
          exclusiveMinimum: 0
        receiveAmount:
          type: number
          exclusiveMinimum: 0
        payCurrency:
          $ref: '#/components/schemas/OnrampPayCurrency'
    Error:
      type: object
      required:
        - code
        - key
        - message
      properties:
        code:
          type: integer
          example: 1500
        key:
          type: string
          example: INSUFFICIENT_BALANCE
        message:
          type: string
          example: Insufficient balance
      additionalProperties: true
    Currency:
      type: string
      enum:
        - USD
        - EUR
    OnrampPayCurrency:
      type: string
      enum:
        - USD
        - EUR
        - GBP
  responses:
    Unauthorized:
      description: >
        Missing/invalid credentials. `key` is one of `UNAUTHORIZED`,
        `MISSING_API_KEY`,

        `INVALID_API_KEY`, `API_KEY_REVOKED`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Envelope'
    Forbidden:
      description: |
        Authenticated but not authorized. `key` is one of `FORBIDDEN`,
        `ACCOUNT_SUSPENDED`, `ACCOUNT_DELETED`, `API_KEY_IP_DENIED`,
        `USER_NOT_OWNED`, `EMAIL_NOT_VERIFIED`. `requireMerchant` routes
        return `FORBIDDEN` when `On-Behalf-Of` is set (sub-user lacks the
        merchant role).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Envelope'
    ValidationFailed:
      description: 422 — Zod or business validation failed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Envelope'
    RateLimited:
      description: 429 — rate limit exceeded.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Envelope'
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: api-key
      description: |
        Your raw API key. Generate, rotate, and revoke keys from the merchant
        dashboard. Keys can optionally be bound to one or more allowed source
        IPs — requests from any other IP are rejected with `API_KEY_IP_DENIED`.

````