Appearance
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/projectGET https://api.sih.market/api/v1/projectINFO
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-webhookGET https://api.sih.market/api/v1/set-webhookINFO
query params:
http
url - https://test.sih.app/callbackurl - https://test.sih.app/callbackINFO
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-itemsGET https://api.sih.market/api/v1/get-itemsINFO
query params:
http
minified - true/false
extended - true/false
appId - 730 / 440 / 252490minified - true/false
extended - true/false
appId - 730 / 440 / 252490INFO
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-itemGET https://api.sih.market/api/v1/get-min-itemINFO
query params:
http
item - M4A4 | Asiimov (Well-Worn)
minified - true/false
appId - 730 / 440 / 252490item - M4A4 | Asiimov (Well-Worn)
minified - true/false
appId - 730 / 440 / 252490INFO
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-orderPOST https://api.sih.market/api/v1/create-orderINFO
description:
text
the "test" parameter in body is used to simulate a purchase
funds will not be debited from your balancethe "test" parameter in body is used to simulate a purchase
funds will not be debited from your balanceINFO
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-orderGET https://api.sih.market/api/v1/get-orderINFO
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-historyGET https://api.sih.market/api/v1/get-order-historyINFO
query params:
http
limit - 100
offset - 0limit - 100
offset - 0INFO
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-ordersPOST https://api.sih.market/api/v1/get-ordersINFO
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:
| Value | Description |
|---|---|
| created | purchase created and awaits item search |
| processing | item was found, waiting for seller to send trade offer |
| sent | item was sent by seller |
| finished | buyer has accepted this offer |
| failed | purchase failed, check error message in order's details |
| penalized | purchase failed with penalty, check error message in order's details |
Protection Statuses
INFO
List:
| Value | Description |
|---|---|
| processing | waiting for the trade protection to expire |
| finished | trade protection successfully |
| failed | the buyer or seller activated trade protection |
Protection Errors
INFO
List:
| Value | Description |
|---|---|
| rollback user | The user has activated trade protection |
| rollback supplier | The 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 failsystem 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 failWallet
Get history
INFO
*Authentication Required
http
GET https://api.sih.market/api/v1/wallet/historyGET https://api.sih.market/api/v1/wallet/historyINFO
search params:
walletTypeId=1
typeIds=1,3,4
limit=100
offset=0
INFO
Wallet type ids:
| Value | Description |
|---|---|
| 1 | Main default wallet |
| 2 | Deposit wallet |
INFO
Transaction Type Ids:
| Value | Description |
|---|---|
| 1 | purchase |
| 2 | purchase refund |
| 3 | transfer between sih.app balance |
| 4 | deposit item |
| 5 | purchase finished refund, fair price system |
| 6 | manual refund |
| 12 | refund 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.

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.

How it works
- A buyer picks one of your listed items on SIH market.
- SIH sends
POST /create-purchaseto your server — "accept this order". - Your server creates the purchase record and starts a Steam trade offer.
- SIH starts polling
GET /get-purchase?customId=…every few seconds and keeps polling until the status isfinished(and the whole protection window closes) orfailed. - 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).
| Method | Endpoint path (suggested) | Purpose |
|---|---|---|
| POST | /api/v1/create-purchase | Accept a new order from SIH |
| GET | /api/v1/get-purchase | Return 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"
}| Field | Type | Description |
|---|---|---|
id | string | Your item id (what's being bought; same id you sent in /project/deposit) |
steamId | string | Buyer SteamID64 |
token | string | Buyer trade token (the part after &token= in the tradelink) |
price | number | Order price in USD |
customId | string | Unique 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'orstatus === 'failed' - After
finished— through the wholeprotectionwindow (~7-10 days) untilprotection.statusisfinishedorfailed
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):
| Value | When to return |
|---|---|
created | Order accepted, trade not initiated yet |
sent | Trade offer dispatched, offerId is set, awaiting buyer to accept |
finished | Buyer accepted the trade; usually accompanied by a protection block |
failed | Trade did not complete; reason explains why |
reason values (only when status === 'failed'):
| Value | Description |
|---|---|
canceled by buyer | Buyer rejected/cancelled the trade offer |
canceled by seller | Seller (your bot) cancelled the trade offer |
send timeout | Trade offer was not dispatched in time |
bad price | The price in create-purchase is below your minimum |
invalid tradelink | Buyer's tradelink is rejected by Steam |
protection block — see Protection Statuses and Protection Errors for the full state machine.
Important field constraints
idmust be a number or a digits-only string. Use1001or"9223372036854775807"(for bigint). Strings with prefixes or letters ("fast-1001") are rejected with awrong-typeschema error.offerIdis camelCase. Notofferid. SIH validators flag the lowercase form aswrong-case.protectionlives insidedata. Putting it at the response root is flagged aswrong-location.- Don't return
reasonfor non-failed statuses. An emptyreason: ""just clutters every response — only include it whenstatus === 'failed'.
Error responses
| Response body | When 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).
| Endpoint | Payload that's signed |
|---|---|
POST /create-purchase | JSON.stringify(req.body) |
GET /get-purchase | JSON.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 payoutPOST /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 payoutFor 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/userPOST https://api.sih.market/api/v1/project/userINFO
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/usersGET https://api.sih.market/api/v1/project/usersINFO
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 onlysteamId+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 | falselimit - <cap> # 1..100 (check=true) | 1..1000 (check=false); default = cap for current mode
offset - 0
check - true # true | falseINFO
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/depositPOST https://api.sih.market/api/v1/project/depositINFO
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/depositsGET https://api.sih.market/api/v1/project/depositsINFO
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-depositPOST https://api.sih.market/api/v1/project/delete-depositINFO
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"
}