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
rescueView on GitHub (pinned to dab8641137)
Solutions
- 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.
- Verify ownership first with GET /api/v2/order: code 2004 tells you the id is not referenceable under this account.
- 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.
- 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
- Re-check order state immediately before cancelling; skip done/cancel states instead of blind-firing cancel.
- Deduplicate cancel requests client-side so double-submits never reach the API.
- In dev, run the matching daemon and RabbitMQ alongside the web tier — cancel is only an AMQP publish.
- Log the full Reason: text; it is the original exception and pinpoints the real cause.
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.