Skip to content

Market!

Market

Authentication

INFO

For authentication, you need provide apikey of your project in headers example:

js
axios.get(
    'https://api.sih.market/api/v1/project',
    {
        headers: {
            apikey: YOUR_API_KEY
        }
    }
)
axios.get(
    'https://api.sih.market/api/v1/project',
    {
        headers: {
            apikey: YOUR_API_KEY
        }
    }
)

Project

Get Project Information

INFO

*Authentication Required

http
GET https://api.sih.market/api/v1/project
GET https://api.sih.market/api/v1/project

INFO

Default Response 200:

json
{
    "success": true,
    "project": {
        "id": 1,
        "name": "test project",
        "balance": 100.51,
        "webhook": "https://test.sih.app/callback"
    }
}
{
    "success": true,
    "project": {
        "id": 1,
        "name": "test project",
        "balance": 100.51,
        "webhook": "https://test.sih.app/callback"
    }
}

INFO

Default Response 400:

json
{
    "success": false,
    "error": "string"
}
{
    "success": false,
    "error": "string"
}

Set Webhook

INFO

Webhook url. If url not provided, webhook will be removed *Authentication Required

http
GET https://api.sih.market/api/v1/set-webhook
GET https://api.sih.market/api/v1/set-webhook

INFO

query params:

http
url - https://test.sih.app/callback
url - https://test.sih.app/callback

INFO

Default Response 200:

json
{
  "success": true
}
{
  "success": true
}

INFO

Default Response 400:

json
{
  "success": false,
  "error": "string"
}
{
  "success": false,
  "error": "string"
}

Item

Get items list

INFO

*Authentication Required

http
GET https://api.sih.market/api/v1/get-items
GET https://api.sih.market/api/v1/get-items

INFO

query params:

http
minified - true/false
extended - true/false
appId - 730 / 440 / 252490
minified - true/false
extended - true/false
appId - 730 / 440 / 252490

INFO

Default Response 200:

json
{
  "success": true,
  "items": {
    "Sealed Graffiti | Tombstone (Monarch Blue)": {
      "price": 1.011,
      "count": 10,
      "phase": "Phase 1",
      "market": "Test Market",
      "sell": 1.01,
      "steam": 1.01,
      "image": "-9a81dlW...",
      "color": "ffffff"
    },
    "Souvenir UMP-45 | Scorched (Field-Tested)": {
      "price": 1.01,
      "count": 10,
      "phase": "Phase 1",
      "market": "Test Market",
      "sell": 1.01,
      "steam": 1.01,
      "image": "-9a81dlW...",
      "color": "ffffff"
    },
    "StatTrak™ FAMAS | Macabre (Factory New)": {
      "price": 1.01,
      "count": 10,
      "phase": "Phase 1",
      "market": "Test Market",
      "sell": 1.01,
      "steam": 1.01,
      "image": "-9a81dlW...",
      "color": "ffffff"
    }
  }
}
{
  "success": true,
  "items": {
    "Sealed Graffiti | Tombstone (Monarch Blue)": {
      "price": 1.011,
      "count": 10,
      "phase": "Phase 1",
      "market": "Test Market",
      "sell": 1.01,
      "steam": 1.01,
      "image": "-9a81dlW...",
      "color": "ffffff"
    },
    "Souvenir UMP-45 | Scorched (Field-Tested)": {
      "price": 1.01,
      "count": 10,
      "phase": "Phase 1",
      "market": "Test Market",
      "sell": 1.01,
      "steam": 1.01,
      "image": "-9a81dlW...",
      "color": "ffffff"
    },
    "StatTrak™ FAMAS | Macabre (Factory New)": {
      "price": 1.01,
      "count": 10,
      "phase": "Phase 1",
      "market": "Test Market",
      "sell": 1.01,
      "steam": 1.01,
      "image": "-9a81dlW...",
      "color": "ffffff"
    }
  }
}

INFO

Default Response 400:

json
{
  "success": false,
  "error": "string"
}
{
  "success": false,
  "error": "string"
}

Get min item price

INFO

*Authentication Required

http
GET https://api.sih.market/api/v1/get-min-item
GET https://api.sih.market/api/v1/get-min-item

INFO

query params:

http
item - M4A4 | Asiimov (Well-Worn)
minified - true/false
appId - 730 / 440 / 252490
item - M4A4 | Asiimov (Well-Worn)
minified - true/false
appId - 730 / 440 / 252490

INFO

Default Response 200:

json
{
  "success": true,
  "items": {
    "M4A4 | Asiimov (Well-Worn)": {
      "price": 93.14,
      "count": 1
    }
  }
}
{
  "success": true,
  "items": {
    "M4A4 | Asiimov (Well-Worn)": {
      "price": 93.14,
      "count": 1
    }
  }
}

INFO

Default Response 400:

json
{
  "success": false,
  "error": "string"
}
{
  "success": false,
  "error": "string"
}

Purchases

Create order

INFO

*Authentication Required

http
POST https://api.sih.market/api/v1/create-order
POST https://api.sih.market/api/v1/create-order

INFO

description:

text
the "test" parameter in body is used to simulate a purchase
funds will not be debited from your balance
the "test" parameter in body is used to simulate a purchase
funds will not be debited from your balance

INFO

body:

json
{
  "steamId": "76561198000000000",
  "token": "SSH18JS",
  "amount": 1.01,
  "item": "AK-47 | Case Hardened (Field-Tested)",
  "customId": "123",
  "test": false,
  "appId": 730
}
{
  "steamId": "76561198000000000",
  "token": "SSH18JS",
  "amount": 1.01,
  "item": "AK-47 | Case Hardened (Field-Tested)",
  "customId": "123",
  "test": false,
  "appId": 730
}

INFO

Default Response 200:

json
{
  "success": true,
  "id": 1,
  "balance": 1.01
}
{
  "success": true,
  "id": 1,
  "balance": 1.01
}

INFO

Default Response 400:

json
{
  "success": false,
  "error": "invalid tradelink | private inventory | steam guard is not enabled | steam trade ban | steam guard is in hold | unknown error"
}
{
  "success": false,
  "error": "invalid tradelink | private inventory | steam guard is not enabled | steam trade ban | steam guard is in hold | unknown error"
}

INFO

Default Response 409:

json
{
  "success": false,
  "error": "custom id already exists",
  "order": {
    "id": 1,
    "customId": "customId",
    "steamId": "76561198000000000",
    "item": "AK-47 | Case Hardened (Field-Tested)",
    "amount": 1,
    "status": "created | processing | sent | finished | failed | penalized",
    "error": "string",
    "sender": {
      "offerId": 1,
      "timeout": 1566796475,
      "nickname": "name",
      "avatar": "https://avatars.akamai.steamstatic.com/975652750497a790ab91b828c1749943b6f6fc8e_medium.jpg"
    },
    "created": 1610000000,
    "updated": 1610000000
  }
}
{
  "success": false,
  "error": "custom id already exists",
  "order": {
    "id": 1,
    "customId": "customId",
    "steamId": "76561198000000000",
    "item": "AK-47 | Case Hardened (Field-Tested)",
    "amount": 1,
    "status": "created | processing | sent | finished | failed | penalized",
    "error": "string",
    "sender": {
      "offerId": 1,
      "timeout": 1566796475,
      "nickname": "name",
      "avatar": "https://avatars.akamai.steamstatic.com/975652750497a790ab91b828c1749943b6f6fc8e_medium.jpg"
    },
    "created": 1610000000,
    "updated": 1610000000
  }
}

Get Order Information

INFO

*Authentication Required

http
GET https://api.sih.market/api/v1/get-order
GET https://api.sih.market/api/v1/get-order

INFO

query params:

http
id - 1
customId - "123"
id - 1
customId - "123"

INFO

Default Response 200:

json
{
  "success": true,
  "order": {
    "id": 1,
    "customId": "customId",
    "steamId": "76561198000000000",
    "item": "AK-47 | Case Hardened (Field-Tested)",
    "amount": 1,
    "expectedAmount": 0.99,
    "status": "created | processing | sent | finished | failed | penalized",
    "error": "string",
    "sender": {
      "offerId": 1,
      "timeout": 1566796475,
      "nickname": "name",
      "avatar": "https://avatars.akamai.steamstatic.com/975652750497a790ab91b828c1749943b6f6fc8e_medium.jpg"
    },
    "protection": {
      "status": "failed",
      "error": "rollback user",
      "rollbackAt": 1753700247,
      "rollbackAmount": 0.99
    },
    "created": 1610000000,
    "updated": 1610000000
  }
}
{
  "success": true,
  "order": {
    "id": 1,
    "customId": "customId",
    "steamId": "76561198000000000",
    "item": "AK-47 | Case Hardened (Field-Tested)",
    "amount": 1,
    "expectedAmount": 0.99,
    "status": "created | processing | sent | finished | failed | penalized",
    "error": "string",
    "sender": {
      "offerId": 1,
      "timeout": 1566796475,
      "nickname": "name",
      "avatar": "https://avatars.akamai.steamstatic.com/975652750497a790ab91b828c1749943b6f6fc8e_medium.jpg"
    },
    "protection": {
      "status": "failed",
      "error": "rollback user",
      "rollbackAt": 1753700247,
      "rollbackAmount": 0.99
    },
    "created": 1610000000,
    "updated": 1610000000
  }
}

INFO

Default Response 400:

json
{
  "success": false,
  "error": "string"
}
{
  "success": false,
  "error": "string"
}

Get Orders History

INFO

*Authentication Required

http
GET https://api.sih.market/api/v1/get-order-history
GET https://api.sih.market/api/v1/get-order-history

INFO

query params:

http
limit - 100
offset - 0
limit - 100
offset - 0

INFO

Default Response 200:

json
{
  "success": true,
  "orders": [
    {
      "id": 1,
      "customId": "customId",
      "steamId": "76561198000000000",
      "item": "AK-47 | Case Hardened (Field-Tested)",
      "amount": 1,
      "expectedAmount": 0.99,
      "status": "created | processing | sent | finished | failed | penalized",
      "error": "string",
      "sender": {
        "offerId": 1,
        "timeout": 1566796475,
        "nickname": "name",
        "avatar": "https://avatars.akamai.steamstatic.com/975652750497a790ab91b828c1749943b6f6fc8e_medium.jpg"
      },
      "protection": {
        "status": "failed",
        "error": "rollback user",
        "rollbackAt": 1753700247,
        "rollbackAmount": 0.99
      },
      "created": 1610000000,
      "updated": 1610000000
    }
  ],
  "pagination": {
    "limit": 100,
    "offset": 0,
    "count": 1000
  }
}
{
  "success": true,
  "orders": [
    {
      "id": 1,
      "customId": "customId",
      "steamId": "76561198000000000",
      "item": "AK-47 | Case Hardened (Field-Tested)",
      "amount": 1,
      "expectedAmount": 0.99,
      "status": "created | processing | sent | finished | failed | penalized",
      "error": "string",
      "sender": {
        "offerId": 1,
        "timeout": 1566796475,
        "nickname": "name",
        "avatar": "https://avatars.akamai.steamstatic.com/975652750497a790ab91b828c1749943b6f6fc8e_medium.jpg"
      },
      "protection": {
        "status": "failed",
        "error": "rollback user",
        "rollbackAt": 1753700247,
        "rollbackAmount": 0.99
      },
      "created": 1610000000,
      "updated": 1610000000
    }
  ],
  "pagination": {
    "limit": 100,
    "offset": 0,
    "count": 1000
  }
}

INFO

Default Response 400:

json
{
  "success": false,
  "error": "string"
}
{
  "success": false,
  "error": "string"
}

Get Orders information

INFO

*Authentication Required

http
POST https://api.sih.market/api/v1/get-orders
POST https://api.sih.market/api/v1/get-orders

INFO

body:

json
{
  "ids": [
    0
  ],
  "customIds": [
    "string"
  ]
}
{
  "ids": [
    0
  ],
  "customIds": [
    "string"
  ]
}

INFO

Default Response 200:

json
{
  "success": true,
  "orders": [
    {
      "id": 1,
      "customId": "customId",
      "steamId": "76561198000000000",
      "item": "AK-47 | Case Hardened (Field-Tested)",
      "amount": 1,
      "expectedAmount": 0.99,
      "status": "created | processing | sent | finished | failed | penalized",
      "error": "string",
      "sender": {
        "offerId": 1,
        "timeout": 1566796475,
        "nickname": "name",
        "avatar": "https://avatars.akamai.steamstatic.com/975652750497a790ab91b828c1749943b6f6fc8e_medium.jpg"
      },
      "protection": {
        "status": "failed",
        "error": "rollback user",
        "rollbackAt": 1753700247,
        "rollbackAmount": 0.99
      },
      "created": 1610000000,
      "updated": 1610000000
    }
  ]
}
{
  "success": true,
  "orders": [
    {
      "id": 1,
      "customId": "customId",
      "steamId": "76561198000000000",
      "item": "AK-47 | Case Hardened (Field-Tested)",
      "amount": 1,
      "expectedAmount": 0.99,
      "status": "created | processing | sent | finished | failed | penalized",
      "error": "string",
      "sender": {
        "offerId": 1,
        "timeout": 1566796475,
        "nickname": "name",
        "avatar": "https://avatars.akamai.steamstatic.com/975652750497a790ab91b828c1749943b6f6fc8e_medium.jpg"
      },
      "protection": {
        "status": "failed",
        "error": "rollback user",
        "rollbackAt": 1753700247,
        "rollbackAmount": 0.99
      },
      "created": 1610000000,
      "updated": 1610000000
    }
  ]
}

INFO

Default Response 400:

json
{
  "success": false,
  "error": "string"
}
{
  "success": false,
  "error": "string"
}

Statuses

INFO

List:

ValueDescription
createdpurchase created and awaits item search
processingitem was found, waiting for seller to send trade offer
sentitem was sent by seller
finishedbuyer has accepted this offer
failedpurchase failed, check error message in order's details
penalizedpurchase failed with penalty, check error message in order's details

Protection Statuses

INFO

List:

ValueDescription
processingwaiting for the trade protection to expire
finishedtrade protection successfully
failedthe buyer or seller activated trade protection

Protection Errors

INFO

List:

ValueDescription
rollback userThe user has activated trade protection
rollback supplierThe supplier has activated trade protection

INFO

Note:

txt
system can change order's status from sent to processing, there is feature allowing to buy an item again if trade wasnt successful because of sender's fail
system can change order's status from sent to processing, there is feature allowing to buy an item again if trade wasnt successful because of sender's fail

Wallet

Get history

INFO

*Authentication Required

http
GET https://api.sih.market/api/v1/wallet/history
GET https://api.sih.market/api/v1/wallet/history

INFO

search params:
walletTypeId=1
typeIds=1,3,4
limit=100
offset=0

INFO

Wallet type ids:

ValueDescription
1Main default wallet
2Deposit wallet

INFO

Transaction Type Ids:

ValueDescription
1purchase
2purchase refund
3transfer between sih.app balance
4deposit item
5purchase finished refund, fair price system
6manual refund
12refund for purchase when canceling a trade via Steam Protection

INFO

Default Response 200:

json
{
  "success": true,
  "data": [
    {
      "id": "527ee278-49e3-4138-b941-f32cc141f03a",
      "amount": -0.1,
      "typeId": 1,
      "data": {
        "orderId": 1
      },
      "created": 1721210563387
    }
  ],
  "pagination": {
    "limit": 100,
    "offset": 0,
    "count": 100
  }
}
{
  "success": true,
  "data": [
    {
      "id": "527ee278-49e3-4138-b941-f32cc141f03a",
      "amount": -0.1,
      "typeId": 1,
      "data": {
        "orderId": 1
      },
      "created": 1721210563387
    }
  ],
  "pagination": {
    "limit": 100,
    "offset": 0,
    "count": 100
  }
}

INFO

Default Response 400:

json
{
  "success": false,
  "error": "unknown error"
}
{
  "success": false,
  "error": "unknown error"
}

Deposit

Description

In our system there are 2 variations to integrate your service p2p and polling Below you can see how they work:

P2P Integration

If you choose P2P, all transactions will go through our p2p system, to sell items, the seller must have the SIH extension installed and have permission for your project to trade.

Deposit!

Polling Integration

When polling is selected, the item shipping status is owned by your system. You implement two REST endpoints on your side, SIH calls them when a buyer places an order and then polls until the trade resolves.

Deposit!

How it works

  1. A buyer picks one of your listed items on SIH market.
  2. SIH sends POST /create-purchase to your server — "accept this order".
  3. Your server creates the purchase record and starts a Steam trade offer.
  4. SIH starts polling GET /get-purchase?customId=… every few seconds and keeps polling until the status is finished (and the whole protection window closes) or failed.
  5. While polling, you return the current state of the purchase using a strict DTO (see Response DTO).

INFO

Reference implementation: a runnable Node/Express example with HMAC verification, lifecycle simulation and graceful shutdown lives at github.com/devsih/polling-example. It implements both endpoints exactly as described below.

Endpoints

You must implement two endpoints on your partner server. The URLs are configurable from the SIH project settings panel (Purchase URL / Check URL).

MethodEndpoint path (suggested)Purpose
POST/api/v1/create-purchaseAccept a new order from SIH
GET/api/v1/get-purchaseReturn the current state of the purchase

Both calls carry an HMAC-SHA1 signature in the signature header — see Signature.

POST /create-purchase

SIH sends this exactly once per order. If you respond with a network error or success: false — the order is moved to failed immediately.

Request body:

json
{
  "id":       "50231",
  "steamId":  "76561198000000000",
  "token":    "SSH18JS",
  "price":    1.23,
  "customId": "fast-12345"
}
{
  "id":       "50231",
  "steamId":  "76561198000000000",
  "token":    "SSH18JS",
  "price":    1.23,
  "customId": "fast-12345"
}
FieldTypeDescription
idstringYour item id (what's being bought; same id you sent in /project/deposit)
steamIdstringBuyer SteamID64
tokenstringBuyer trade token (the part after &token= in the tradelink)
pricenumberOrder price in USD
customIdstringUnique order id from SIH — your idempotency key, save it as-is

Response uses the same DTO as GET /get-purchase.

GET /get-purchase

SIH polls this periodically:

  • Every few seconds until status === 'finished' or status === 'failed'
  • After finished — through the whole protection window (~7-10 days) until protection.status is finished or failed

Query: ?customId=fast-12345 — the same customId SIH sent in create-purchase.

Headers: signature: HMAC-SHA1({customId}, depositApiKey) (hex)

Response DTO

A single schema, with optional fields appearing only in matching states.

ts
{
  success: true,
  data: {
    id:       number,                       // YOUR purchase id (not the item id from request)
    price:    number,
    offerId:  number | null,                // Steam trade offer id; null until status='sent'
    status:   'created' | 'sent' | 'finished' | 'failed',
    avatar:   string,                       // seller-bot avatar URL
    nickname: string,                       // seller-bot nickname

    // Only when status === 'failed':
    reason?:  'canceled by buyer' | 'canceled by seller'
           |  'send timeout' | 'bad price' | 'invalid tradelink',

    // Only when status === 'finished' — protection window:
    protection?: {
      status:          'processing' | 'finished' | 'failed',
      until?:          number,              // unix sec, when the window closes
      error?:          'rollback user' | 'rollback supplier',
      rollbackAt?:     number,              // unix sec, when the trade was rolled back
      rollbackAmount?: number               // how much you refund to the buyer
    }
  }
}
{
  success: true,
  data: {
    id:       number,                       // YOUR purchase id (not the item id from request)
    price:    number,
    offerId:  number | null,                // Steam trade offer id; null until status='sent'
    status:   'created' | 'sent' | 'finished' | 'failed',
    avatar:   string,                       // seller-bot avatar URL
    nickname: string,                       // seller-bot nickname

    // Only when status === 'failed':
    reason?:  'canceled by buyer' | 'canceled by seller'
           |  'send timeout' | 'bad price' | 'invalid tradelink',

    // Only when status === 'finished' — protection window:
    protection?: {
      status:          'processing' | 'finished' | 'failed',
      until?:          number,              // unix sec, when the window closes
      error?:          'rollback user' | 'rollback supplier',
      rollbackAt?:     number,              // unix sec, when the trade was rolled back
      rollbackAmount?: number               // how much you refund to the buyer
    }
  }
}

Status values (data.status):

ValueWhen to return
createdOrder accepted, trade not initiated yet
sentTrade offer dispatched, offerId is set, awaiting buyer to accept
finishedBuyer accepted the trade; usually accompanied by a protection block
failedTrade did not complete; reason explains why

reason values (only when status === 'failed'):

ValueDescription
canceled by buyerBuyer rejected/cancelled the trade offer
canceled by sellerSeller (your bot) cancelled the trade offer
send timeoutTrade offer was not dispatched in time
bad priceThe price in create-purchase is below your minimum
invalid tradelinkBuyer's tradelink is rejected by Steam

protection block — see Protection Statuses and Protection Errors for the full state machine.

Important field constraints

  • id must be a number or a digits-only string. Use 1001 or "9223372036854775807" (for bigint). Strings with prefixes or letters ("fast-1001") are rejected with a wrong-type schema error.
  • offerId is camelCase. Not offerid. SIH validators flag the lowercase form as wrong-case.
  • protection lives inside data. Putting it at the response root is flagged as wrong-location.
  • Don't return reason for non-failed statuses. An empty reason: "" just clutters every response — only include it when status === 'failed'.

Error responses

Response bodyWhen to use
{ success: false, error: "invalid signature" }HMAC mismatch on either endpoint
{ success: false, error: "not found" }customId is unknown on your side (only in get-purchase)
{ success: false, error: "bad price" }create-purchase price below minimum
{ success: false, error: "invalid tradelink" }create-purchase payload validation failed

All errors must be returned with HTTP 200 and success: false. Returning 4xx/5xx triggers a network-level failure on the SIH side and may cause unnecessary retries.

Signature

Every call between SIH and your server carries an HMAC-SHA1 signature in the signature header. The shared secret is your project's depositApiKey (generate it in the Sandbox tab of your project settings).

EndpointPayload that's signed
POST /create-purchaseJSON.stringify(req.body)
GET /get-purchaseJSON.stringify({ customId })
js
const crypto = require('node:crypto')

function createSignature(data, apiKey) {
  return crypto
    .createHmac('sha1', apiKey)
    .update(JSON.stringify(data))
    .digest('hex')
}
const crypto = require('node:crypto')

function createSignature(data, apiKey) {
  return crypto
    .createHmac('sha1', apiKey)
    .update(JSON.stringify(data))
    .digest('hex')
}

Verify in constant time

Use crypto.timingSafeEqual(Buffer.from(provided), Buffer.from(expected)) to compare — a naive === leaks timing information that can be used to brute the signature byte-by-byte.

Idempotency

SIH may retry POST /create-purchase if the previous call timed out or hit a network error. The retry carries the same customId.

Your handler must detect this and return the existing purchase record instead of creating a duplicate:

js
const existing = await db.purchases.findByCustomId(customId)
if (existing) return res.send({ success: true, data: toDto(existing) })
const existing = await db.purchases.findByCustomId(customId)
if (existing) return res.send({ success: true, data: toDto(existing) })

"not found" semantics

If customId is unknown on your side, return { success: false, error: "not found" }.

The SIH worker tolerates this for up to 15 minutes — after that, the purchase is marked failed (error: "order is not found"). This protects against permanently-stuck orders if the partner's storage was wiped or the record was never created due to a bug.

Lifecycle example

For a happy-path order the polling responses progress through these states:

text
POST /create-purchase
└── { success: true, data: { id: 1001, status: 'created', offerId: null, ... } }

GET /get-purchase (called every few seconds)
├── { ..., status: 'created', offerId: null }                     ← trade not started yet
├── { ..., status: 'sent',     offerId: 1234567890 }              ← offer dispatched
├── { ..., status: 'finished', protection: { status: 'processing', until: 1769740800 } }
│                                                                  ← buyer accepted; protection window opens
└── { ..., status: 'finished', protection: { status: 'finished',  until: 1769136000 } }
                                                                   ← protection closed; seller payout
POST /create-purchase
└── { success: true, data: { id: 1001, status: 'created', offerId: null, ... } }

GET /get-purchase (called every few seconds)
├── { ..., status: 'created', offerId: null }                     ← trade not started yet
├── { ..., status: 'sent',     offerId: 1234567890 }              ← offer dispatched
├── { ..., status: 'finished', protection: { status: 'processing', until: 1769740800 } }
│                                                                  ← buyer accepted; protection window opens
└── { ..., status: 'finished', protection: { status: 'finished',  until: 1769136000 } }
                                                                   ← protection closed; seller payout

For a rollback during the protection window:

text
└── { ..., status: 'finished', protection: {
        status:         'failed',
        error:          'rollback user',          // or 'rollback supplier'
        rollbackAt:     1769740800,
        rollbackAmount: 0.98                      // refund to buyer
      }
    }
└── { ..., status: 'finished', protection: {
        status:         'failed',
        error:          'rollback user',          // or 'rollback supplier'
        rollbackAt:     1769740800,
        rollbackAmount: 0.98                      // refund to buyer
      }
    }

Testing your implementation

The Sandbox tab of your project settings (sandbox.sih.app → your project → Sandbox) ships with:

  • Reachability tests — sends two requests with intentionally broken signatures and verifies you return { success: false, error: "invalid signature" }
  • Live activity log — every real SIH→partner call over the last 24 hours with schema validation and bad-field highlighting
  • DTO Reference — 16 example responses showing what to return in each scenario (happy, failed reasons, rollback variants)

The reference Express implementation at github.com/devsih/polling-example passes all sandbox checks out of the box — clone it as a starting point.

Minimal Express example

js
const express = require('express')
const bodyParser = require('body-parser')
const crypto = require('node:crypto')

const app = express()
app.use(bodyParser.json())

function createSignature(data, apiKey) {
  return crypto.createHmac('sha1', apiKey).update(JSON.stringify(data)).digest('hex')
}

const depositApiKey = 'YOUR_DEPOSIT_API_KEY'

// POST /api/v1/create-purchase
app.post('/api/v1/create-purchase', async (req, res) => {
  // 1. Verify signature
  if (req.headers.signature !== createSignature(req.body, depositApiKey)) {
    return res.send({ success: false, error: 'invalid signature' })
  }

  const { id, steamId, token, price, customId } = req.body

  // 2. Idempotency — return existing record on retry
  const existing = await db.purchases.findByCustomId(customId)
  if (existing) return res.send({ success: true, data: toDto(existing) })

  // 3. Validate price / tradelink (your business rules)
  if (!(await isPriceValid(id, price))) {
    return res.send({ success: false, error: 'bad price' })
  }

  // 4. Create the purchase and start a Steam trade (async)
  const purchase = await db.purchases.create({
    customId, itemId: id, steamId, token, price, status: 'created',
  })
  void initiateSteamTrade(purchase)

  return res.send({ success: true, data: toDto(purchase) })
})

// GET /api/v1/get-purchase
app.get('/api/v1/get-purchase', async (req, res) => {
  const { customId } = req.query

  if (req.headers.signature !== createSignature({ customId }, depositApiKey)) {
    return res.send({ success: false, error: 'invalid signature' })
  }

  const purchase = await db.purchases.findByCustomId(customId)
  if (!purchase) return res.send({ success: false, error: 'not found' })

  return res.send({ success: true, data: toDto(purchase) })
})

// Internal Purchase → DTO mapping
function toDto(p) {
  const data = {
    id:       p.id,
    price:    p.price,
    offerId:  p.offerId,           // camelCase! Not offerid.
    status:   p.status,
    avatar:   p.bot.avatar,
    nickname: p.bot.nickname,
  }
  if (p.status === 'failed' && p.reason) data.reason = p.reason
  if (p.status === 'finished' && p.protection) data.protection = p.protection
  return data
}

app.listen(3000)
const express = require('express')
const bodyParser = require('body-parser')
const crypto = require('node:crypto')

const app = express()
app.use(bodyParser.json())

function createSignature(data, apiKey) {
  return crypto.createHmac('sha1', apiKey).update(JSON.stringify(data)).digest('hex')
}

const depositApiKey = 'YOUR_DEPOSIT_API_KEY'

// POST /api/v1/create-purchase
app.post('/api/v1/create-purchase', async (req, res) => {
  // 1. Verify signature
  if (req.headers.signature !== createSignature(req.body, depositApiKey)) {
    return res.send({ success: false, error: 'invalid signature' })
  }

  const { id, steamId, token, price, customId } = req.body

  // 2. Idempotency — return existing record on retry
  const existing = await db.purchases.findByCustomId(customId)
  if (existing) return res.send({ success: true, data: toDto(existing) })

  // 3. Validate price / tradelink (your business rules)
  if (!(await isPriceValid(id, price))) {
    return res.send({ success: false, error: 'bad price' })
  }

  // 4. Create the purchase and start a Steam trade (async)
  const purchase = await db.purchases.create({
    customId, itemId: id, steamId, token, price, status: 'created',
  })
  void initiateSteamTrade(purchase)

  return res.send({ success: true, data: toDto(purchase) })
})

// GET /api/v1/get-purchase
app.get('/api/v1/get-purchase', async (req, res) => {
  const { customId } = req.query

  if (req.headers.signature !== createSignature({ customId }, depositApiKey)) {
    return res.send({ success: false, error: 'invalid signature' })
  }

  const purchase = await db.purchases.findByCustomId(customId)
  if (!purchase) return res.send({ success: false, error: 'not found' })

  return res.send({ success: true, data: toDto(purchase) })
})

// Internal Purchase → DTO mapping
function toDto(p) {
  const data = {
    id:       p.id,
    price:    p.price,
    offerId:  p.offerId,           // camelCase! Not offerid.
    status:   p.status,
    avatar:   p.bot.avatar,
    nickname: p.bot.nickname,
  }
  if (p.status === 'failed' && p.reason) data.reason = p.reason
  if (p.status === 'finished' && p.protection) data.protection = p.protection
  return data
}

app.listen(3000)

TIP

This is the bare-bones structure. For a full implementation with HMAC constant-time verify, lifecycle simulation, Doppler-phase handling and graceful shutdown — see github.com/devsih/polling-example.

Add user

INFO

*Authentication Required

http
POST https://api.sih.market/api/v1/project/user
POST https://api.sih.market/api/v1/project/user

INFO

INFO

body:

json
{
  "steamId": "76561198000000000",
  "tradeToken": "SSH18JS"
}
{
  "steamId": "76561198000000000",
  "tradeToken": "SSH18JS"
}

INFO

Default Response 200:

json
{
  "success": true,
  "data": {
    "steamId": "string",
    "canTrade": {
      "success": "boolean",
      "error": "string"
    },
    "canP2P": {
      "success": "boolean",
      "error": "string"
    }
  }
}
{
  "success": true,
  "data": {
    "steamId": "string",
    "canTrade": {
      "success": "boolean",
      "error": "string"
    },
    "canP2P": {
      "success": "boolean",
      "error": "string"
    }
  }
}

INFO

Default Response 400:

json
{
  "success": true,
  "data": {
    "steamId": "string",
    "canTrade": {
      "success": "boolean",
      "error": "string"
    },
    "canP2P": {
      "success": "boolean",
      "error": "string"
    }
  }
}
{
  "success": true,
  "data": {
    "steamId": "string",
    "canTrade": {
      "success": "boolean",
      "error": "string"
    },
    "canP2P": {
      "success": "boolean",
      "error": "string"
    }
  }
}

Get user info

INFO

*Authentication Required

http
GET https://api.sih.market/api/v1/project/user/${steamId}
GET https://api.sih.market/api/v1/project/user/${steamId}

INFO

Default Response 200:

json
{
  "success": true,
  "data": {
    "steamId": "string",
    "canTrade": {
      "success": "boolean",
      "error": "string"
    },
    "canP2P": {
      "success": "boolean",
      "error": "string"
    }
  }
}
{
  "success": true,
  "data": {
    "steamId": "string",
    "canTrade": {
      "success": "boolean",
      "error": "string"
    },
    "canP2P": {
      "success": "boolean",
      "error": "string"
    }
  }
}

Get users list

INFO

*Authentication Required

http
GET https://api.sih.market/api/v1/project/users
GET https://api.sih.market/api/v1/project/users

INFO

Returns a paginated list of bots attached to the project. Two modes:

  • check=true (default) — validates each bot against Steam (canTrade / canP2P). Heavy: one Steam round-trip per user. Max page size 100.
  • check=false — lightweight DB list, returns only steamId + online. Max page size 1000.

INFO

query params:

http
limit  - <cap>      # 1..100 (check=true) | 1..1000 (check=false); default = cap for current mode
offset - 0
check  - true       # true | false
limit  - <cap>      # 1..100 (check=true) | 1..1000 (check=false); default = cap for current mode
offset - 0
check  - true       # true | false

INFO

Default Response 200 (check=true):

json
{
  "success": true,
  "data": [{
    "steamId": "76561198000000000",
    "canTrade": {
      "success": true,
      "error": ""
    },
    "canP2P": {
      "success": true,
      "error": ""
    }
  }],
  "pagination": {
    "limit": 100,
    "offset": 0,
    "total": 234,
    "hasMore": true
  }
}
{
  "success": true,
  "data": [{
    "steamId": "76561198000000000",
    "canTrade": {
      "success": true,
      "error": ""
    },
    "canP2P": {
      "success": true,
      "error": ""
    }
  }],
  "pagination": {
    "limit": 100,
    "offset": 0,
    "total": 234,
    "hasMore": true
  }
}

INFO

Default Response 200 (check=false):

json
{
  "success": true,
  "data": [{
    "steamId": "76561198000000000",
    "online": true
  }],
  "pagination": {
    "limit": 1000,
    "offset": 0,
    "total": 234,
    "hasMore": false
  }
}
{
  "success": true,
  "data": [{
    "steamId": "76561198000000000",
    "online": true
  }],
  "pagination": {
    "limit": 1000,
    "offset": 0,
    "total": 234,
    "hasMore": false
  }
}

INFO

Default Response 400 — limit exceeds cap, invalid limit/offset, or project not found:

json
{
  "success": false,
  "error": "limit 2000 exceeds the cap of 1000 for check=false. Use check=false for larger pages (cap 1000)."
}
{
  "success": false,
  "error": "limit 2000 exceeds the cap of 1000 for check=false. Use check=false for larger pages (cap 1000)."
}

Set User Online

INFO

*Authentication Required

http
POST https://api.sih.market/api/v1/project/user/${steamId}/online/${online}
POST https://api.sih.market/api/v1/project/user/${steamId}/online/${online}

INFO

Default Response 200:

json
{
  "success": true,
  "data": {
    "online": "boolean"
  }
}
{
  "success": true,
  "data": {
    "online": "boolean"
  }
}

INFO

Default Response 400:

json
{
  "success": false,
  "error": "string"
}
{
  "success": false,
  "error": "string"
}

Set Users Online

INFO

*Authentication Required

http
POST https://api.sih.market/api/v1/project/users/online/${online}
POST https://api.sih.market/api/v1/project/users/online/${online}

INFO

INFO

body:

json
{
  "steamIds": [
    "76561198000000000",
    "76561198000000001"
  ]
}
{
  "steamIds": [
    "76561198000000000",
    "76561198000000001"
  ]
}

INFO

Default Response 200:

json
{
  "success": true,
  "data": {
    "online": true
  }
}
{
  "success": true,
  "data": {
    "online": true
  }
}

INFO

Default Response 400:

json
{
  "success": false,
  "error": "string"
}
{
  "success": false,
  "error": "string"
}

Deposit user items

INFO

*Authentication Required

http
POST https://api.sih.market/api/v1/project/deposit
POST https://api.sih.market/api/v1/project/deposit

INFO

INFO

body:

items['id'] - asset id of user item items['price'] - price for user item

json
{
  "steamId": "string",
  "appId": 0,
  "items": [
    {
      "id": "123", 
      "price": 0,
      "customId": "string",
      "amount": 1
    }
  ]
}
{
  "steamId": "string",
  "appId": 0,
  "items": [
    {
      "id": "123", 
      "price": 0,
      "customId": "string",
      "amount": 1
    }
  ]
}

INFO

Default Response 200:

json
{
  "success": true,
  "errors": [
    "string"
  ],
  "data": [
    {
      "id": "string",
      "item": "string",
      "price": 0,
      "assetid": "string",
      "amount": 1,
      "float": 0,
      "phase": "string",
      "stickers": [
        "string"
      ]
    }
  ]
}
{
  "success": true,
  "errors": [
    "string"
  ],
  "data": [
    {
      "id": "string",
      "item": "string",
      "price": 0,
      "assetid": "string",
      "amount": 1,
      "float": 0,
      "phase": "string",
      "stickers": [
        "string"
      ]
    }
  ]
}

INFO

Default Response 400:

json
{
  "success": false,
  "error": "string",
  "data": {
    "steamId": "string",
    "canTrade": {
      "success": true,
      "error": "string"
    },
    "canP2P": {
      "success": true,
      "error": "string"
    }
  }
}
{
  "success": false,
  "error": "string",
  "data": {
    "steamId": "string",
    "canTrade": {
      "success": true,
      "error": "string"
    },
    "canP2P": {
      "success": true,
      "error": "string"
    }
  }
}

List active deposit items

INFO

*Authentication Required

http
GET https://api.sih.market/api/v1/project/deposits
GET https://api.sih.market/api/v1/project/deposits

INFO

Returns all your deposits that are still on sale — items in status new or paused. The full list is returned in one response (no pagination).

Response item fields:

data['id'] - our internal item id (the same id returned by /project/deposit and accepted by /project/delete-deposit) data['customId'] - the custom id you passed on deposit data['price'] - item price

INFO

Default Response 200:

json
{
  "success": true,
  "data": [
    {
      "id": "string",
      "customId": "string",
      "price": 0
    }
  ]
}
{
  "success": true,
  "data": [
    {
      "id": "string",
      "customId": "string",
      "price": 0
    }
  ]
}

INFO

Default Response 400:

json
{
  "success": false,
  "error": "string"
}
{
  "success": false,
  "error": "string"
}

Delete deposit user items

INFO

*Authentication Required

http
POST https://api.sih.market/api/v1/project/delete-deposit
POST https://api.sih.market/api/v1/project/delete-deposit

INFO

INFO

body:

json
{
  "ids": [
    "string"
  ],
  "customIds": [
    "string"
  ]
}
{
  "ids": [
    "string"
  ],
  "customIds": [
    "string"
  ]
}

INFO

Default Response 200:

json
{
  "success": true,
  "data": [
    "string"
  ]
}
{
  "success": true,
  "data": [
    "string"
  ]
}

INFO

Default Response 400:

json
{
  "success": false,
  "error": "string"
}
{
  "success": false,
  "error": "string"
}