Raw API
Design your own experience. Register users, buy, sell and read balances server-to-server — no OroPocket UI.
- Your server:
Register the user
Trusted registration returns a persistent user_code. You own their UX and their OTP — we never message them.
POST/partner /users /init - Your server:
Lock a quote
Quote a rupee amount of gold or silver. The rate and the price block it was struck against are frozen.
POST/partner /buy /quote Held for 10 minutes.
- Your server:
Confirm it
Send the quote_id with an Idempotency-Key. Your float is debited and the user is credited.
POST/partner /buy /confirm A replay returns the original response rather than buying twice.
- OroPocket:
It settles, or it refunds
If fulfilment fails after the debit, the wallet is credited back and the order voided automatically.
- OroPocket:
We call your webhook
Signed, timestamped and retried across six hours. Selling mirrors the same quote-then-confirm shape.
Register a user
Trusted registration returns a persistent user_code. You own the user's UX and OTP; we never message them.
/partner/users/initcurl -X POST https://api.oropocket.com/partner/users/init \
-H "Authorization: Bearer oro_live_xxx" \
-d '{"flow":"api","mobile":"9000000001","name":"Anya"}'Buy
Two steps — quote, then confirm. Quotes lock a price for a short window.
/partner/buy/quote| Field | Type | Description | |
|---|---|---|---|
| user_code | string | required | The end user to buy for. |
| asset_type | string | required | "gold" or "silver". |
| amount_inr | number | required | Amount to spend in INR. |
/partner/buy/confirm# quote → returns quote_id + grams + rate
curl -X POST https://api.oropocket.com/partner/buy/quote \
-H "Authorization: Bearer oro_live_xxx" \
-d '{"user_code":"usr_xxx","asset_type":"gold","amount_inr":1000}'
# confirm
curl -X POST https://api.oropocket.com/partner/buy/confirm \
-H "Authorization: Bearer oro_live_xxx" \
-H "Idempotency-Key: <uuid>" \
-d '{"quote_id":"tqt_xxx"}'Merchant markup (Pro)
On the Pro plan you set your own spread on our rates — added to buy, taken off sell. Your users are quoted the marked-up price, your wallet is debited the full marked-up amount on a buy, your user is credited the reduced amount on a sell, and the difference comes back to you on your monthly statement — Billing works the money through end to end. The same pair of percentages also prices the hosted checkout and anything you render from GET /partner/prices, so there is one number to keep straight, not three. Here is how to set it.
live_enabled tells you which side of that you are on./partner/dashboard/markup/partner/dashboard/markupNo capability needed — unlike every other endpoint on this page, the two markup calls are authorised by your token alone, so they work whatever your allowed_apis grants are.
Reading it returns your current setting and whether your plan allows one:
| Field | Type | Description |
|---|---|---|
| markup_buy_pct | number | Your buy spread, added to our rate. 0 means off, which is the default for every partner. |
| markup_sell_pct | number | Your sell spread, deducted from the proceeds. Independent of the buy side — one can be 0 and the other not. |
| max_pct | number | The platform cap, currently 5, and it applies to each side separately. A stored value above it is clamped rather than honoured. |
| available | boolean | Whether your plan allows a markup. Free-tier tokens get false here rather than a 403 — so you can render it as a locked upsell instead of hiding it. |
| live_enabled | boolean | Whether we have enabled markup on your live account. False means your live prices are unchanged no matter what you store here; sandbox ignores this. |
| applied | boolean | The honest answer to “is this moving prices right now?” — true only when your plan allows it, the gate for this mode is open, and at least one percentage is above 0. |
| pending_approval | boolean | You have set a spread but live is not enabled yet. Worth surfacing in your own admin UI so the number does not look broken. |
| sell_supported | boolean | False only on the older schema, where the sell field does not exist yet. Hide the sell control rather than writing to it. |
| tier | string | "paid" or "free". |
Setting it takes either field, or both. A field you omit is left alone, so you can change one side without reading the other first:
| Field | Type | Description | |
|---|---|---|---|
| markup_buy_pct | number | optional | 0 to max_pct (5), up to three decimal places. Send 0 to switch the buy side off. |
| markup_sell_pct | number | optional | 0 to max_pct (5), same rules. Send 0 to switch the sell side off. Sending neither field is an error. |
# read
curl https://api.oropocket.com/partner/dashboard/markup \
-H "Authorization: Bearer oro_live_xxx"
# 2% on buys, 1% on sells
curl -X PUT https://api.oropocket.com/partner/dashboard/markup \
-H "Authorization: Bearer oro_live_xxx" \
-H "Content-Type: application/json" \
-d '{"markup_buy_pct": 2, "markup_sell_pct": 1}'
# change only the sell side
curl -X PUT https://api.oropocket.com/partner/dashboard/markup \
-H "Authorization: Bearer oro_live_xxx" \
-H "Content-Type: application/json" \
-d '{"markup_sell_pct": 0.5}'Unlike the read, this one refuses rather than silently storing something else:
| Error | When |
|---|---|
| 403 PRO_REQUIRED | You are on the free plan and tried to set a non-zero markup. Setting it to 0 is always allowed. |
| 400 MARKUP_TOO_HIGH | Above max_pct. The message names the current cap. |
| 400 INVALID_MARKUP | Negative, or not a number. |
What changes on a buy quote. Once a markup is in effect, POST /partner/buy/quote carries three extra fields. When it isn't — the default, and every free-tier partner — the response is exactly as documented above, with no extra fields at all, so an existing integration sees no change until you set one.
| Field | Type | Description |
|---|---|---|
| rate_inr_per_gram | number | What your user is charged — our rate plus your markup. Same field as always; on a marked-up quote it is the marked-up rate. |
| base_rate_inr_per_gram | number | Our rate before your markup. Present only when a markup applied. |
| markup_pct | number | The percentage that was applied, after the 5% platform cap. |
| markup_amount_inr | number | Rupee value of your markup on this order. Returned to you on your monthly statement. |
Sell
Sell mirrors buy — quote then confirm. Proceeds settle into the end user's INR wallet, not your float.
/partner/sell/quotesell| Field | Type | Description | |
|---|---|---|---|
| user_code | string | required | Whose holdings to sell. |
| asset_type | string | required | "gold" or "silver". |
| quantity_grams | number | optional | Exact grams to sell. Provide this OR amount_inr. |
| amount_inr | number | optional | Target NET rupees the user should receive — we work backwards to the grams. Provide this OR quantity_grams. If you have a sell markdown set, it comes out on top: we sell slightly more grams so your user still receives exactly this figure, and the spread is yours. |
A sell markdown never comes out of amount_inr. Ask for ₹15,000 with a 2% markdown and your user is credited ₹15,000 — we sell about 2% more metal and hand you the difference. It is the mirror of the buy side, where a fixed rupee amount buys your user slightly less metal. quantity_grams quotes are unaffected: there the markdown reduces the payout, because the quantity is fixed.
| Field | Type | Description |
|---|---|---|
| quote_id | string | Pass to /sell/confirm. |
| quantity_grams | number | Grams that will be sold. |
| rate_inr_per_gram | number | Locked sell rate. |
| net_amount_inr | number | What the user actually receives, after fees. |
| breakdown | object | gross_amount_inr, platform_fee_grams, platform_fee_inr, platform_fee_percentage (0.25), net_amount_inr, target_net_mode, proceeds_destination ('user_inr_wallet'), price_block_id. |
| expires_in_seconds | number | Quote TTL — 10 minutes. |
/partner/sell/confirmsell| Field | Type | Description | |
|---|---|---|---|
| quote_id | string | required | From /sell/quote. |
| Idempotency-Key | header | optional | Recommended — makes a retry safe. |
Users, portfolio & balances
/partner/usersusersLists the users you registered. Add ?include=balances for a per-user holdings snapshot, and ?scope=all to also include embed-verified and attributed users (each row then carries a sources array). Paginate with limit (1–200) and offset.
/partner/users/:user_codeusersOne user's detail. Identity is deliberately masked — you address users only by user_code.
| Field | Type | Description |
|---|---|---|
| user_code | string | Your handle for this user. |
| name | string | As supplied at registration. |
| mobile_masked | string | e.g. XXXXXX3210 — the full number is never returned. |
| email_masked | string | e.g. a***@example.com. |
| kyc_status | string | not_started | under_review | approved | rejected. |
| user_existed | boolean | True if the OroPocket account predated your registration (so they are not attributed to you). |
| registered_at | string | When they were linked to you. |
| balances | object | Only with ?include=balances — your sub-wallet view of their holdings. |
/partner/users/:user_code/portfoliobalanceHoldings for one user. Values are scoped to your sub-wallet, not the user's whole OroPocket balance — read sub-wallets before you reconcile against these numbers.
curl "https://api.oropocket.com/partner/users?include=balances" \
-H "Authorization: Bearer oro_live_xxx"
curl "https://api.oropocket.com/partner/users/usr_xxx?include=balances" \
-H "Authorization: Bearer oro_live_xxx"Users must complete KYC before they can withdraw. Configure webhooks to get settlement events.