Skip to content

Create Deposit ​

POST /api/v1/merchant/create-deposit

Creates a deposit with specific Steam items. Used in the API-only integration flow after fetching inventory.

Request ​

Headers ​

HeaderRequiredDescription
X-Api-Key✅Your merchant API key
Content-Type✅application/json

Body ​

ParameterTypeRequiredDescription
merchant_tx_idstring✅Your unique transaction/order ID
gamestring✅Game code: csgo, rust, dota2, tf2
partnerinteger✅Steam trade partner ID
tokenstring✅Steam trade token
asset_idsstring[]✅Item IDs from the inventory response. Must not exceed max_items (see Get Inventory)
result_urlstring—Webhook URL — overrides your merchant-level webhook URL for this deposit (must be valid URL)
success_urlstring—Success redirect URL (must be valid URL)
fail_urlstring—Failure redirect URL (must be valid URL)
custom_currencystring—Custom currency code (1–4 chars, e.g. BTC, EUR). Requires all 3 currency fields
custom_currency_multipliernumber—Currency multiplier (0.5–2.0)
custom_currency_ratenumber—Currency exchange rate (≥ 0.1)
min_pricesobject—Per-item price floor in USD, keyed by asset_id (e.g. {"38029384123": 19.50}). If an item's price at deposit time is below its floor, the deposit is rejected with item_specified_price_not_found and no trade is created. Entries ≤ 0 are ignored
from_previewboolean—Credit the deposit at the prices shown by Deposit Preview. Defaults to false. Requires preview access on your account; without it the flag is ignored and the deposit runs the regular flow. See Deposit at Preview Prices

Pricing & Price Lock ​

The deposit amount is derived from the inventory snapshot of your most recent Get Inventory call for the same Steam account (partner + token). That snapshot is cached for 5 minutes.

  • If you call create-deposit more than 5 minutes after fetching the inventory, the request fails with inventory_reload — re-fetch the inventory and retry.
  • Without min_prices, the credited amount is the price cached at deposit time, not the moment you displayed it to the user.

Protect against price drift with min_prices

Pass the optional min_prices map (keyed by asset_id, in USD) to set a per-item floor. If any item's price at deposit time is below its floor, the deposit is rejected with item_specified_price_not_found and no trade is created — so you are never credited less than you displayed. Set each floor slightly below the price you showed the user (e.g. quoted minus 1–2%). Prices are USD regardless of any custom_currency fields.

Alternative: hash-name flow

Get Prices → Create Deposit by Hash Name offers the same per-item min_price protection plus a per-transaction quote snapshot (by id), so the quote cannot be overwritten by a concurrent inventory fetch.

Deposit at Preview Prices (from_preview) ​

Access on request

This flow is enabled per merchant account, together with Deposit Preview itself. Until we enable it for yours, from_preview is ignored: the deposit is still created, but through the regular flow described above — including its 5-minute Get Inventory snapshot rule. Ask your account manager to turn it on.

Set from_preview: true to credit the deposit at the prices Deposit Preview shows — the flow for carts built from the preview:

  1. Call Deposit Preview and show the user our prices.
  2. The user picks items ("the cart").
  3. Call create-deposit with the picked asset_ids and from_preview: true.

The credited amount equals the current preview pricing of exactly those items — if a price has updated since the user saw it, the current price is credited. No prior Get Inventory call is required, and the 5-minute inventory_reload snapshot rule does not apply.

Rejections ​

Both rejections happen before anything is created: no deposit record, no webhook, and your merchant_tx_id is not consumed — simply retry with the same ID.

price_changed — the cart cannot be credited as it stands: an item can no longer be accepted, or we cannot accept the cart at these prices right now. data carries the current pricing of the same cart, in the Deposit Preview response shape, and data.rejected names every item that blocks it — re-show these prices, drop the rejected items, and retry. No extra preview call is needed:

json
{
  "success": false,
  "message": "price_changed",
  "data": {
    "items": [
      { "id": "38029384123", "name": "AK-47 | Redline (Field-Tested)", "price": 12.10 }
    ],
    "rejected": [
      { "id": "51819053033", "reason": "not_in_inventory" }
    ],
    "total": 1,
    "sum": 12.10,
    "game": "csgo",
    "max_items": 50
  }
}
rejected[].reasonMeaning
not_in_inventoryThe asset ID is not in the user's inventory — drop it from the cart
not_quotedWe do not accept this item right now — drop it from the cart

data.items lists what we would still credit, with current prices — items in rejected are not part of it.

preview_unavailable — preview pricing is temporarily unavailable. Retry later, or fall back to the regular Get Inventory → create-deposit flow.

from_preview vs min_prices

min_prices protects a regular deposit against price drift with a floor you compute yourself. from_preview needs no floor: the cart is credited at our current preview prices.

Response ​

json
{
  "success": true,
  "message": "Deposit processing with selected items",
  "data": {
    "id": 42,
    "merchant_tx_id": "order-12345",
    "status": "active",
    "amount": 36.25,
    "bot_name": "Skinslink Bot #3",
    "bot_steam_id": 76561199012345678,
    "trade_offer_id": "6912345678",
    "trade_offer_expiry_at": "2026-02-16T12:30:00Z",
    "success_url": "https://yoursite.com/deposit/success",
    "fail_url": "https://yoursite.com/deposit/failed"
  }
}

Response Fields ​

FieldTypeDescription
idintegerInternal trade ID
merchant_tx_idstringYour transaction ID (echoed back)
statusstringTrade status (active on success, failed on error)
amountnumberTotal deposit amount (USD)
bot_namestring | nullName of the Steam bot sending the offer
bot_steam_idinteger | nullSteam ID of the bot
trade_offer_idstring | nullSteam trade offer ID
trade_offer_expiry_atstring | nullISO 8601 timestamp when the offer expires
success_urlstringSuccess redirect URL (only present if provided in request)
fail_urlstringFailure redirect URL (only present if provided in request)

INFO

Custom currency fields (custom_currency, custom_currency_multiplier, custom_currency_rate, custom_currency_sum) are not included in the create-deposit response. Use Deposit Status to retrieve them once the trade reaches hold or completed status.

Example ​

bash
curl -X POST https://api.skinslink.com/api/v1/merchant/create-deposit \
  -H "X-Api-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "merchant_tx_id": "order-12345",
    "game": "csgo",
    "partner": 123456789,
    "token": "AbCdEfGh",
    "asset_ids": ["38029384123", "38029384124"]
  }'

To add a per-item price floor, include min_prices in the request body:

json
{
  "merchant_tx_id": "order-12346",
  "game": "csgo",
  "partner": 123456789,
  "token": "AbCdEfGh",
  "asset_ids": ["38029384123", "38029384124"],
  "min_prices": {
    "38029384123": 19.50,
    "38029384124": 4.20
  }
}

After Creating a Deposit ​

  1. Show the trade offer info to the user (bot name, offer ID)
  2. User accepts the trade offer in Steam
  3. Listen for webhooks or poll /merchant/deposit/status for updates

TIP

Store the trade_offer_expiry_at and remind users to accept before expiry. Expired offers are automatically canceled.

Errors ​

StatusMessageCause
400invalid request bodyMalformed JSON
400validation errorMissing or invalid fields (see data array for details). Includes inventory_reload — the inventory price snapshot expired (> 5 min since Get Inventory); re-fetch and retry
400item_specified_price_not_foundAn item's price at deposit time dropped below its min_prices floor. No trade is created; re-fetch the inventory for current prices
400deposit exceeds the maximum of N items per depositasset_ids has more items than the per-deposit limit — read max_items from Get Inventory. No trade is created; split into smaller deposits
401missing authorization headerX-Api-Key header not provided
401invalid API keyAPI key not found or inactive
403merchant account is disabledMerchant account deactivated
403IP not whitelisted: <ip>Request IP not in whitelist
404not found errorTrade or items not found
409already exist errorDuplicate merchant_tx_id
500internal errorSomething went wrong on our side

→ For domain-specific validation codes (Steam account issues, trade eligibility), see Errors.