Skip to main content
SkinShark gives every merchant the ability to provision sub-users — isolated accounts under one merchant key. Each sub-user has its own wallet, own Steam trade URLs, own trade history. From your end users’ perspective, the merchant is invisible; from the SkinShark side, every action is attributable to a specific sub-user. The bridge between your merchant API key and any one sub-user is a single header.

The header

The value can be:
  • The sub-user’s UUID (returned by POST /merchant/users), or
  • The externalId you assigned when creating them.
Both resolve to the same sub-user. UUIDs are stable; external IDs are yours to choose. Most teams set externalId to their internal user ID (user_42, acme_customer_8129) and never persist the SkinShark UUID.

Where the header applies

With @skinshark/sdk, you can either pass onBehalfOf per call or bind a sub-user once with await sdk.as(ref) and call methods on the returned client — it injects the header for every call. See the SDK guide for the full pattern. Without the header on a sub-user-context route, the call runs as the merchant itself. That’s intentional: it lets the merchant trade for its own account when needed.

Examples

The buy debits user_42’s wallet, uses their primary Steam trade URL, and the resulting trade shows up under their history.

Resolving the value

The server resolves both UUID and externalId against parentId = your merchant. A UUID that belongs to a different merchant returns USER_NOT_FOUND — never FORBIDDEN — so other merchants’ UUIDs can’t be enumerated through error responses.

Mistakes to watch for

The requireMerchant guard checks the resolved role. Sub-users don’t have the merchant role, so the call returns FORBIDDEN with code 1300. Drop the header for merchant-context routes (or just don’t send it on that path).
The buy succeeds — but it buys for the merchant. If you wanted to buy for a customer, you’ll see the listing in your merchant trade history, not theirs, and the merchant’s wallet was debited.Defensive pattern: always set On-Behalf-Of when handling a customer request, never derive intent from absence:
externalId is a server-side trust boundary. If your client lets a user pass an arbitrary ID and you forward it as On-Behalf-Of, you’ve just given that user the ability to act as any of your sub-users. Always derive the value from your own session/auth, not request input.

Suspended sub-users

If a sub-user is suspended (via POST /merchant/users/{id}/suspend), every On-Behalf-Of resolution to that user returns:
Trades, deposits, and balance reads all fail with this. Reactivate with POST /merchant/users/{id}/reactivate.