hpyhacking/peatio · error · APIv2::CancelOrderError

2003

2003

Error message

Failed to cancel order. Reason: #{e}

What it means

Peatio APIv2 error code 2003 (HTTP 400), raised by POST /api/v2/order/delete. The endpoint runs current_user.orders.find(params[:id]) then Ordering.new(order).cancel, which only publishes a cancel command to the RabbitMQ matching queue (app/services/ordering.rb). A bare rescue catches ANY failure — ActiveRecord::RecordNotFound when the id is not yours or does not exist, or a Bunny/AMQP publish error — and re-raises it as CancelOrderError with the original exception embedded in "Reason: #{$!}". Cancelling an order that is already done or cancelled does NOT fail here: the publish still succeeds and the matching engine just logs a skip.

Source

Thrown at app/api/api_v2/orders.rb:67

    params do
      use :auth, :market, :order
    end
    post "/orders" do
      order = create_order params
      present order, with: APIv2::Entities::Order
    end

    desc 'Cancel an order.', scopes: %w(trade)
    params do
      use :auth, :order_id
    end
    post "/order/delete" do
      begin
        order = current_user.orders.find(params[:id])
        Ordering.new(order).cancel
        present order, with: APIv2::Entities::Order
      rescue
        raise CancelOrderError, $!
      end
    end

    desc 'Cancel all my orders.', scopes: %w(trade)
    params do
      use :auth
      optional :side, type: String, values: %w(sell buy), desc: "If present, only sell orders (asks) or buy orders (bids) will be canncelled."
    end
    post "/orders/clear" do
      begin
        orders = current_user.orders.with_state(:wait)
        if params[:side].present?
          type = params[:side] == 'sell' ? 'OrderAsk' : 'OrderBid'
          orders = orders.where(type: type)
        end
        orders.each {|o| Ordering.new(o).cancel }
        present orders, with: APIv2::Entities::Order
      rescue

View on GitHub (pinned to dab8641137)

Solutions

  1. Read the Reason: text — 'Couldn't find Order' means the id is wrong or not yours; a Bunny/AMQP error means the message bus is unreachable.
  2. Verify ownership first with GET /api/v2/order: code 2004 tells you the id is not referenceable under this account.
  3. If the reason is a broker connection error, restore RabbitMQ and the matching daemon, then re-issue the cancel — nothing was applied because the publish failed.
  4. Make the client idempotent: treat a 2003 whose cause is RecordNotFound as 'already gone' success, not as an error.

Example fix

# before
post '/order/delete', id: id   # 2003 aborts the flow

# after: verify, cancel, treat already-gone as success
begin
  order = get '/order', id: id                      # 2004 => not yours/gone
  post '/order/delete', id: id if order['state'] == 'wait'
rescue ApiError => e
  raise unless e.code == 2003 && e.reason =~ /Couldn't find Order/
  # order already gone: nothing to cancel
end
Defensive patterns

Strategy: try-catch

Validate before calling

# Pre-flight: only cancel orders you own and that are still active
order = client.get '/api/v2/order', id: id            # 2004 => not yours / gone
if order['state'] == 'wait'
  client.post '/api/v2/order/delete', id: id
end

Type guard

// Narrow the parsed error body before deciding
const isCancelError = (e: unknown): e is { error: { code: 2003; message: string } } =>
  typeof e === 'object' && (e as any)?.error?.code === 2003;

Try / catch

Catch code 2003 and branch on Reason: 'Couldn't find Order' => treat as success (already gone, remove from local state); a Bunny/AMQP connection error => wait for broker recovery and re-issue once; anything else => surface to logs and stop.

Prevention

When it happens

Trigger: POST /order/delete with an id belonging to another member or a nonexistent id (Reason: Couldn't find Order with id=...); RabbitMQ unreachable or the matching daemon down so AMQPQueue.enqueue raises inside Ordering#cancel; a dropped AMQP channel on the web process.

Common situations: Race where the order gets fully filled between the bot's last state poll and the cancel call, followed by a retry with a stale id; duplicate submission of the same cancel; development setups where the web tier runs but the matching daemon does not; signing with the wrong access key so find() scopes to another member's orders.

Related errors


AI-assisted analysis of hpyhacking/peatio@dab8641137 (2026-08-23). Data as JSON: /api/errors/4b614220afa4a6c0. Report an issue: GitHub.