> ## Documentation Index
> Fetch the complete documentation index at: https://docs.matambaintelligence.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Place an order

> Stores the order, then sends it to the exchange once. If an order ends in `uncertain`, we don't send it again. Retrieve it, or wait for its event, until it resolves.



## OpenAPI

````yaml /openapi.json post /v1/orders
openapi: 3.1.0
info:
  title: Matamba Gateway
  version: '1'
  description: >-
    Open brokerage accounts for your customers, fund them from your wallet,
    place and track orders, and read each account's records at the broker. All
    amounts are integers in kobo, and every error includes a `code` you can use
    in your logic.
servers:
  - url: https://{host}
    description: The sandbox and live environments each have their own host and API keys.
    variables:
      host:
        default: sandbox-host.invalid
        description: We send you the host along with your API keys.
security:
  - bearer: []
tags:
  - name: Accounts
    description: The accounts you can trade for, and the broker's records for each one.
  - name: Orders
    description: >-
      Estimate, place, retrieve and cancel orders. We store each order before we
      send it to the exchange, and we send it only once.
  - name: Events
    description: >-
      Each change to your orders creates an event. We send the same event bodies
      to your webhook endpoint. See [Webhooks](/webhooks) for how to verify
      them.
  - name: Applications
    description: Open a brokerage account for your customer.
  - name: Reference
    description: Code lists from the broker that you use in account applications.
  - name: Wallet
    description: >-
      Your wallet, and the allocations you make from it to your customers'
      accounts.
paths:
  /v1/orders:
    post:
      tags:
        - Orders
      summary: Place an order
      description: >-
        Stores the order, then sends it to the exchange once. If an order ends
        in `uncertain`, we don't send it again. Retrieve it, or wait for its
        event, until it resolves.
      operationId: placeOrder
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        $ref: '#/components/requestBodies/Order'
      responses:
        '200':
          $ref: '#/components/responses/Order'
          description: >-
            This idempotency key was already used for this same order, so we
            didn't send anything new. The body shows the order's current state.
        '201':
          $ref: '#/components/responses/Order'
          description: This request created the order. `state` is `pending` or `uncertain`.
        '400':
          $ref: '#/components/responses/Error'
          description: >-
            `invalid_request`, `missing_required_field` (`param` names the field
            or header), `unknown_account`. We didn't store anything.
        '401':
          $ref: '#/components/responses/Error'
          description: '`unauthorized`'
        '409':
          $ref: '#/components/responses/Error'
          description: >-
            `duplicate_order`: this idempotency key was already used for a
            different order, which `message` names. Retrieve that order instead
            of sending again. `similar_order_unresolved`: an identical order on
            this account hasn't resolved yet. We didn't store anything.
        '413':
          $ref: '#/components/responses/Error'
          description: '`request_too_large`'
        '415':
          $ref: '#/components/responses/Error'
          description: '`unsupported_media_type`: send `Content-Type: application/json`.'
        '422':
          $ref: '#/components/responses/Error'
          description: >-
            `order_rejected`: the exchange rejected the order, and
            `reject_reason` says why. The order is stored as `rejected`, and you
            can't reuse the key. `unknown_symbol`, `limit_exceeded`: we didn't
            store anything, so you can reuse the key.
        '429':
          $ref: '#/components/responses/Error'
          description: >-
            `rate_limited`: we didn't process the request. Retry it with the
            same key after `Retry-After` seconds.
        '500':
          $ref: '#/components/responses/Error'
          description: '`internal_error`: retry the request with the same key.'
        '502':
          $ref: '#/components/responses/Error'
          description: >-
            `exchange_error`: we didn't store anything. Retrying won't help, so
            contact us.
        '503':
          $ref: '#/components/responses/Error'
          description: >-
            `exchange_unavailable`: we didn't store or send anything. Retry with
            the same key. `order_not_sent`: the order is stored as `refused`,
            and you can't reuse the key. Retry with a new key.
            `service_unavailable`: retry with the same key, not a new one.
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: >-
        A key you choose to identify this request. Send the same key every time
        you retry the request. Once used, a key stays tied to that request
        permanently. It must be printable ASCII with no spaces, and we compare
        it byte for byte. Send the header only once.
      schema:
        type: string
        minLength: 1
        maxLength: 255
        pattern: ^[!-~]+$
  requestBodies:
    Order:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/OrderRequest'
          examples:
            limit:
              summary: Limit buy of an ETF (limit orders are for ETFs only)
              value:
                account: '1000000001'
                symbol: STANBICETF30
                side: buy
                quantity: 100
                type: limit
                time_in_force: day
                price_kobo: 12050
            market:
              summary: Market sell
              value:
                account: '1000000001'
                symbol: MTNN
                side: sell
                quantity: 50
                type: market
                time_in_force: day
  responses:
    Order:
      description: An order.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestId'
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Order'
          examples:
            placed:
              summary: A market buy, just placed
              value:
                object: order
                id: ord_62jxryztxctob7uopekg
                state: pending
                account: '1000000001'
                symbol: DANGCEM
                side: buy
                quantity: 10
                type: market
                time_in_force: day
                price_kobo: null
                filled_quantity: 0
                exchange_order_id: '100001'
                reason: null
                reject_reason: null
                created_at: '2026-09-25T06:12:34Z'
                updated_at: '2026-09-25T06:12:34Z'
            filled:
              summary: The same order, filled
              value:
                object: order
                id: ord_62jxryztxctob7uopekg
                state: filled
                account: '1000000001'
                symbol: DANGCEM
                side: buy
                quantity: 10
                type: market
                time_in_force: day
                price_kobo: null
                filled_quantity: 10
                exchange_order_id: '100001'
                reason: null
                reject_reason: null
                created_at: '2026-09-25T06:12:34Z'
                updated_at: '2026-09-25T06:13:02Z'
            limit:
              summary: A limit buy of an ETF, open (what a cancel returns)
              value:
                object: order
                id: ord_7kq2m4xw5pz3ncvha6de
                state: pending
                account: '1000000001'
                symbol: STANBICETF30
                side: buy
                quantity: 50
                type: limit
                time_in_force: day
                price_kobo: 12000
                filled_quantity: 0
                exchange_order_id: '100002'
                reason: null
                reject_reason: null
                created_at: '2026-09-25T06:20:11Z'
                updated_at: '2026-09-25T06:20:11Z'
    Error:
      description: >-
        An error. Use `error.code` in your logic. A path that doesn't exist
        returns 404 `not_found`, and a method the path doesn't support returns
        405 `method_not_allowed` with an `Allow` header.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestId'
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        WWW-Authenticate:
          $ref: '#/components/headers/WWWAuthenticate'
        Allow:
          $ref: '#/components/headers/Allow'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    OrderRequest:
      type: object
      description: >-
        Send numbers as JSON numbers, not quoted strings. We reject unknown
        fields and any field that appears twice, even with different letter
        case.
      required:
        - account
        - symbol
        - side
        - quantity
        - type
        - time_in_force
      additionalProperties: false
      properties:
        account:
          $ref: '#/components/schemas/AccountCode'
        symbol:
          type: string
          pattern: ^[A-Za-z0-9.-]{1,20}$
          description: >-
            The exchange's symbol, treated as uppercase. The exchange decides
            whether the symbol exists.
        side:
          type: string
          enum:
            - buy
            - sell
        quantity:
          type: integer
          format: int64
          minimum: 1
          maximum: 100000000
          description: The number of shares, as a whole number.
        type:
          type: string
          enum:
            - limit
            - market
          description: >-
            Limit orders are for ETFs only. Use a market order for any other
            security. The broker rejects a limit order on any other security
            with `reject_reason` set to `limit_orders_etf_only`. A market order
            is priced at the day's price limit: the upper limit for a buy, the
            lower limit for a sell.
        time_in_force:
          type: string
          enum:
            - day
            - gtc
            - ioc
            - fok
          description: >-
            `day`: good for the current trading session only. `gtc`: good till
            cancelled. `ioc`: immediate or cancel. `fok`: fill or kill. We
            accept every `type` with every `time_in_force`. The broker decides
            which combinations it supports, and rejects the rest as
            `order_rejected` with a `reject_reason`.
        price_kobo:
          type: integer
          format: int64
          minimum: 1
          maximum: 1000000000
          description: >-
            The limit price in kobo, so ₦475.00 is 47500. Required for a limit
            order. Omit it for a market order.
    Order:
      type: object
      required:
        - object
        - id
        - state
        - account
        - symbol
        - side
        - quantity
        - type
        - time_in_force
        - price_kobo
        - filled_quantity
        - exchange_order_id
        - reason
        - reject_reason
        - created_at
        - updated_at
      properties:
        object:
          type: string
          const: order
        id:
          type: string
          description: Unique identifier for the order. Starts with `ord_`.
        state:
          $ref: '#/components/schemas/OrderState'
        account:
          type: string
          description: The account code, in uppercase.
        symbol:
          type: string
          description: The symbol, in uppercase.
        side:
          type: string
          enum:
            - buy
            - sell
        quantity:
          type: integer
          format: int64
        type:
          type: string
          enum:
            - limit
            - market
        time_in_force:
          type: string
          enum:
            - day
            - gtc
            - ioc
            - fok
        price_kobo:
          type:
            - integer
            - 'null'
          format: int64
          description: The limit price in kobo. Null for a market order.
        filled_quantity:
          type: integer
          format: int64
          description: The total number of shares traded so far.
        exchange_order_id:
          type:
            - string
            - 'null'
          description: The exchange's identifier for the order, once it has one.
        reason:
          type:
            - string
            - 'null'
          description: >-
            A sentence explaining the order's current state. It's for people to
            read, so don't use it in your logic.
        reject_reason:
          type:
            - string
            - 'null'
          description: >-
            Why the exchange rejected the order. Set only when `state` is
            `rejected`.
          enum:
            - insufficient_funds
            - market_closed
            - settlement_lockout
            - limit_orders_etf_only
            - unknown_symbol
            - symbol_not_tradable
            - account_not_at_broker
            - rejected_by_exchange
            - null
        created_at:
          type: string
          format: date-time
          description: In UTC, to the second.
        updated_at:
          type: string
          format: date-time
          description: In UTC, to the second.
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - type
            - code
            - message
            - doc_url
          properties:
            type:
              type: string
              description: The category of the error.
              enum:
                - authentication_error
                - invalid_request_error
                - idempotency_error
                - order_error
                - application_error
                - wallet_error
                - limit_error
                - rate_limit_error
                - api_error
            code:
              type: string
              description: The error code. Use it in your logic; it doesn't change.
              enum:
                - unauthorized
                - not_found
                - method_not_allowed
                - unsupported_media_type
                - request_too_large
                - invalid_request
                - missing_required_field
                - unknown_account
                - duplicate_order
                - order_not_found
                - exchange_unavailable
                - order_not_sent
                - order_rejected
                - similar_order_unresolved
                - order_not_cancelable
                - cancel_unconfirmed
                - settlement_lockout
                - account_not_at_broker
                - exchange_error
                - application_rejected
                - duplicate_application
                - application_not_found
                - application_not_sent
                - wallet_too_low
                - allocation_rejected
                - duplicate_allocation
                - allocation_not_found
                - allocation_not_sent
                - rate_limited
                - unknown_symbol
                - limit_exceeded
                - service_unavailable
                - internal_error
            message:
              type: string
              description: >-
                A human-readable description of the error, for your logs. The
                wording can change.
            param:
              type: string
              description: >-
                The field, parameter or header that caused the error. Absent
                when the error isn't tied to a single field.
            reject_reason:
              type: string
              description: >-
                Why the exchange or broker rejected the request. Included with
                `order_rejected`, `application_rejected` and
                `allocation_rejected`, and takes the same values as the
                `reject_reason` field on the order, application or allocation.
            doc_url:
              type: string
              format: uri
              description: A link to the documentation for this error code.
    AccountCode:
      type: string
      pattern: ^[A-Za-z0-9]{1,64}$
      description: An account code. Case-insensitive on input, and returned in uppercase.
    OrderState:
      type: string
      description: >-
        `sending`: stored, and the send is in progress. `pending`: the exchange
        has the order. `partially_filled`, `filled`: some or all of the order
        has traded. `uncertain`: we don't know whether the exchange received it.
        We don't send it again; we reconcile it against the exchange's record.
        `rejected`: the exchange rejected it; see `reject_reason`. `refused`: we
        refused it, and it never reached the exchange. `cancelled`: the exchange
        confirmed the cancel. `expired`: its time in force ran out.
      enum:
        - sending
        - pending
        - partially_filled
        - filled
        - uncertain
        - rejected
        - refused
        - cancelled
        - expired
  headers:
    RequestId:
      description: A unique identifier for this request. Include it when you contact us.
      schema:
        type: string
    CacheControl:
      description: Always `no-store`.
      schema:
        type: string
        const: no-store
    RetryAfter:
      description: >-
        The number of seconds to wait before you retry. Sent with 429 (`1`) and
        with 503 `settlement_lockout` (`60`).
      schema:
        type: integer
    WWWAuthenticate:
      description: 'Sent with 401: `Bearer realm="Matamba Gateway"`.'
      schema:
        type: string
    Allow:
      description: Sent with 405. Lists the methods the path supports.
      schema:
        type: string
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      description: >-
        `Authorization: Bearer <key>`. Sandbox keys start with `mgw_test_` and
        live keys start with `mgw_live_`. Each key works only in its own
        environment, and a live key works only from the IP addresses or ranges
        registered for it.

````