How the sandbox behaves
| Symbols | Four symbols are available: DANGCEM at ₦475.00, MTNN at ₦250.00, ZENITHBANK at ₦38.50, and STANBICETF30 (an ETF) at ₦120.50. |
| Fills | An order that crosses the listed price fills at once, in full, at that price. A market order always crosses, a limit buy crosses at or above the listed price, and a limit sell at or below it. A sell fills only if the account holds every share it sells: if it holds fewer, none of the order fills. |
| Unfilled orders | An ioc or fok order that doesn’t fill at once expires at once. Any other stays open: a day order until midnight, West Africa Time (WAT), when it expires, and a gtc order until you cancel it. Only a limit order can be cancelled, so an unfilled gtc market sell stays open. |
| Market hours | The sandbox market never closes, so you can test at any time. |
| Balance | Every account starts with ₦1,000,000.00, except the account ending in 99, which starts with ₦0. |
| Wallet | Your sandbox wallet opens with ₦10,000,000.00 of test money when we set up your access, credited once. For more, ask us. See Fund an account. |
| Price limits | 10% either side of the listed price. A market buy is priced at the upper limit and a market sell at the lower limit. |
| Fees | The sandbox charges a flat 1.5%. Live fees are set by the broker: for its fee schedule, ask us. |
| Reset | A restart of the sandbox, such as for a new release, resets its broker: every account’s balance and positions start again, and an order still open there ends expired within 30 seconds. Your keys, your webhook endpoint and our records of your orders, applications, allocations, events and wallet stay. Your wallet isn’t reset, so money allocated before a restart stays in allocated_kobo but is no longer in the accounts’ balances. The sandbox’s broker never gives a number twice, so an exchange_order_id, an application’s reference or a new account is never one you’ve seen before. |
Test your error handling
Before you go live, send these requests to check that your integration handles each error.| Request | Response |
|---|---|
A request to any endpoint without the Authorization header | 401 unauthorized. |
A request with any key starting mgw_live_ | 401 unauthorized, with a message that it’s a live key and this is the sandbox. |
GET /v1/nothing | 404 not_found. |
PUT /v1/accounts | 405 method_not_allowed, with an Allow header. |
POST /v1/orders with Content-Type: text/plain | 415 unsupported_media_type. |
| A request body over 16 KiB | 413 request_too_large. |
An order with "quantity": "10", a number in quotes | 400 invalid_request. Send numbers as JSON numbers. |
An order without symbol | 400 missing_required_field, with param set to symbol. |
| An order for an account that isn’t yours | 400 unknown_account. |
GET /v1/accounts/0000000000 | 404 account_not_found. |
GET /v1/orders/ord_doesnotexist22222222 | 404 order_not_found. |
GET /v1/applications/app_doesnotexist22222222 | 404 application_not_found. |
GET /v1/allocations/alc_doesnotexist22222222 | 404 allocation_not_found. |
Buy on the account ending in 99 | 422 order_rejected with reject_reason: insufficient_funds, because the account has a zero balance. |
Place a limit order on DANGCEM | 422 order_rejected with reject_reason: limit_orders_etf_only. Limit orders are accepted only for ETFs. |
| Place a market order for a symbol that isn’t listed above | 422 unknown_symbol. The order isn’t stored, so you can reuse the idempotency key. |
| Place a limit buy for a symbol that isn’t listed above | 422 order_rejected with reject_reason: unknown_symbol. Only a market order or a sell is checked before we store it, so a limit buy reaches the broker, which rejects it. |
Reuse an Idempotency-Key with a different order, such as step 3’s key with quantity 11 | 409 duplicate_order, with order naming the order that owns the key. |
More than 40 requests sent together, such as 80 GET /v1/accounts | 429 rate_limited, with a Retry-After: 1 header. |
A market buy of 20,000 DANGCEM | 422 limit_exceeded: counted at the day’s upper price limit, it’s more than the default day’s order value. Nothing is stored. |
| Cancel a market order | 409 order_not_cancelable. Only a limit order can be cancelled. |
While an order for 2,222 shares is uncertain (see Test values), send another identical order with a new key | 409 similar_order_unresolved, with order naming the uncertain order. Nothing is stored. |
| An allocation of more than your sandbox wallet holds, within the day’s allocation limit | 422 wallet_too_low. Nothing is stored. |
| An allocation of ₦60,000,000 | 422 limit_exceeded: it’s more than the default day’s allocations. Nothing is stored. |
Reuse an application’s Idempotency-Key with a different form | 409 duplicate_application, with application naming the application that owns the key. |
Reuse an allocation’s Idempotency-Key with a different amount | 409 duplicate_allocation, with allocation naming the allocation that owns the key. |
Test values
Each row shows one outcome on demand, only in the sandbox. For the order quantities, use a limit buy ofSTANBICETF30 below ₦120.50, such as price_kobo 12000, so the order stays open.
| Send | What happens |
|---|---|
| A market order | 201 pending. Within 30 seconds it becomes filled, with an order.filled event. |
| An order for exactly 1,111 shares | 201 pending. 444 shares fill at once at your price and the rest stays open, so within 30 seconds you receive order.partially_filled. |
| An order for exactly 2,222 shares | 201 uncertain: the sandbox withholds the broker’s response to the order. Within about two minutes, reconciliation finds it at the broker and it becomes pending, with an order.pending event. |
| A cancel of an open limit order | 202. Within 30 seconds the order becomes cancelled. |
| A cancel of an open order of exactly 3,333 shares | 503 cancel_unconfirmed: the cancel reaches the broker, and the sandbox withholds its response. Within 30 seconds the order becomes cancelled. |
A limit ioc or fok order that doesn’t cross the listed price | 201 pending. It expires at once, and within 30 seconds it becomes expired. |
An account ending in 98 | The broker is settling the day’s trades for it. Its balance: 503 settlement_lockout with Retry-After: 60. An estimate or an order: 422 order_rejected with reject_reason: settlement_lockout. Its positions, trades and contract notes read as usual, and an allocation to it is booked. Ask us to add one to your sandbox. |
| An application | 201 submitted. The sandbox’s broker opens it, and within 30 seconds it becomes opened, with an application.opened event that carries the new account. |
An application with bvn 00000000000 | 422 application_rejected with reject_reason: rejected_by_broker. Nothing reaches the sandbox’s broker, so you can send it again. In live, contact us before you send it again. |
A second application with a bvn or an email the sandbox has already accepted, from any partner | 422 application_rejected with reject_reason: already_at_broker. Use a new BVN and email for each application. |
An application with an email without an @ | 422 application_rejected with reject_reason: invalid_field and param set to email. Correct it and send it with a new key. |
| An allocation to one of your accounts | 201 booked, with an allocation.booked event. |
| Outcome | Why |
|---|---|
An order in refused | Only an order we could not send to the broker at all. |
An order in sending | An order is sending only while its request is in progress, or after it if its outcome was not recorded. No test value causes either. |
An order rejected with account_not_at_broker | The sandbox’s broker opens any ten-digit account the first time it’s used. |
An order rejected with market_closed | The sandbox market never closes. |
An order rejected with rejected_by_exchange | Only when the broker can’t accept an order as we sent it, rejects an order after accepting it, or our operations team records a rejection. |
An order rejected with symbol_not_tradable | Every symbol the sandbox lists can be traded. |
An application in refused | Only an application we could not send to the broker at all. |
An application in sending | An application is sending only while its request is in progress, or after it if its outcome was not recorded. No test value causes either. |
An application in uncertain | No test value withholds the broker’s response to an application. |
An application in unplaced | Only our operations team records that the broker does not hold an application. |
An application rejected with declined_by_broker | Only our operations team records that the broker declined an application after accepting it. |
An allocation in refused | Only an allocation we could not send to the broker at all. |
An allocation in rejected | No test value makes the sandbox’s broker refuse a deposit, so no allocation reject reason can be shown. |
An allocation in sending | An allocation is sending only while its request is in progress, or after it if its outcome was not recorded. No test value causes either. |
An allocation in uncertain | No test value withholds the broker’s response to an allocation. |
| The limit on requests in progress at once | Not on demand: the sandbox’s broker answers at once, so your requests are rarely in progress together. |
account_not_at_broker | The sandbox’s broker opens any ten-digit account the first time it’s used. |
allocation_not_sent | Only an allocation we could not send to the broker at all. |
allocation_rejected | No test value makes the sandbox’s broker refuse a deposit. |
application_not_sent | Only an application we could not send to the broker at all. |
exchange_error | Only when the broker’s response can’t be processed. |
exchange_unavailable | Only while we can’t reach the broker. |
feature_not_enabled | Account opening and deposits are enabled on the sandbox for every partner. |
insufficient_scope | Only with a key that lacks a scope. Ask us for one to test with. |
internal_error | Only on an internal gateway error. |
order_not_sent | Only an order we could not send to the broker at all. |
outcome_not_recorded | Only when the outcome of a new order, application or allocation was not recorded. No test value causes it. |
service_unavailable | Only when the gateway is temporarily unable to process the request. |
similar_allocation_unresolved | Only while an allocation is unresolved, and no test value withholds the broker’s response to an allocation. |
trading_paused | Only while our operations team has paused trading. |
| A live key from an unregistered address | Sandbox keys work from any address. |
| Linking a customer the broker already knows | Our operations team does it with the broker, outside the API. |