Skip to Content
API ReferenceStop & Take Orders

Stop & take orders

A stop or take order waits until the mark price of a property reaches a price that you set. This price is the trigger price. When the mark price reaches it, Loaf places a limit order for you. The API calls these orders conditional orders.

Use them to:

  • Limit a loss on a position (stop-loss).
  • Lock in a profit on a position (take-profit).
  • Buy when the price rises above a level, or falls to a level.

Loaf does not reserve your USDC or tokens when you create a stop or take order. Loaf reserves them only when the order triggers. Authenticate with your API key from the web app API section.

Endpoints

MethodPathDescription
POST/api/orders/conditionalCreate a stop or take order
POST/api/orders/conditional/cancelCancel a stop or take order
POST/api/orders/Create a buy with a take-profit and a stop-loss attached (tpPrice, slPrice)

How stop and take orders work

Order types

TypeWhen the order triggers, Loaf places
STOP_MARKETA limit order at a protected price, calculated from the trigger price
TAKE_MARKETA limit order at a protected price, calculated from the trigger price
STOP_LIMITA limit order at the price that you set
TAKE_LIMITA limit order at the price that you set

The type and the side set the direction. The type alone does not:

TypeA BUY triggers when the mark priceA SELL triggers when the mark price
STOP_MARKET, STOP_LIMITRises to the trigger price or aboveFalls to the trigger price or below
TAKE_MARKET, TAKE_LIMITFalls to the trigger price or belowRises to the trigger price or above

Common uses:

GoalType and sideTrigger price
Stop-loss on a positionSTOP_MARKET SELLBelow the mark price
Take-profit on a positionTAKE_MARKET SELLAbove the mark price
Buy when the price rises above a levelSTOP_MARKET BUYAbove the mark price
Buy when the price falls to a levelTAKE_MARKET BUYBelow the mark price

Mark price

Loaf compares the trigger price with the mark price of the property. This is the same price that the markprice:{tokenName} WebSocket channel sends. The mark price is the midpoint between the best bid and the best ask.

An order triggers when the mark price is at the trigger price or past it, in the direction of the order. The mark price does not have to cross the trigger price while you watch. For example, if the market opens with the mark price already past your trigger price, your order triggers at the open.

Price of the limit order

Loaf sets the price of the limit order when you create the stop or take order. This price does not change.

  • STOP_LIMIT and TAKE_LIMIT: the price is the price that you send.
  • STOP_MARKET and TAKE_MARKET: send price: 0. Loaf calculates a protected price from the trigger price and maxSlippageBps, the slippage limit for market orders. Get maxSlippageBps from Property market detail. Loaf rounds the result to the nearest cent.
SideProtected price
BUYtriggerPrice × (1 + maxSlippageBps / 10000)
SELLtriggerPrice × (1 - maxSlippageBps / 10000)

Example: maxSlippageBps is 300 (3%). A STOP_MARKET SELL with a trigger price of 90 gets a protected price of 87.30.

A STOP_MARKET or TAKE_MARKET order is not a market order. When it triggers, Loaf places a limit order at a fixed price. If the market moves past that price quickly, the limit order stays on the book. It does not fill until the market comes back to its price.

Status

StatusMeaning
PENDINGA take-profit or stop-loss that is attached to a buy. It waits for the buy to fill completely.
ARMEDActive. Loaf compares the trigger price with the mark price.
PLACEDTriggered. Loaf placed the limit order. placedOrderId is the id of that order.
FAILEDLoaf could not place the limit order. rejectionReason gives the reason.
CANCELLEDCancelled by you, by Cancel all orders, or by the linked order (see Attach take-profit and stop-loss to a buy).

PLACED, FAILED, and CANCELLED are final. After PLACED, follow the limit order placedOrderId for fills. In rare cases, the exchange rejects that limit order. Then the status changes from PLACED to FAILED.

A rejectionReason on an ARMED order shows a temporary problem. The order is still active, and Loaf tries again. It is not a failure.

When the order triggers

  1. Loaf places a limit order with the side, price, and quantity of the stop or take order.
  2. The limit order must pass the same checks as an order that you place yourself. Examples are the daily price band and the minimum order value.
  3. If your USDC or tokens are locked in your own open orders on the same side of the same property, Loaf cancels those orders first. Then it places the limit order.

If a check fails, or you do not have enough USDC or tokens, the status changes to FAILED. Loaf does not try again.

A stop or take order can cancel your other orders when it triggers. For example, if a stop-loss triggers while your limit sell is on the book, Loaf cancels the limit sell to free the tokens. You then get CANCELLED updates for orders that you did not cancel. Loaf does not cancel your orders on other properties.

Things to know

  • You cannot change a stop or take order. To change it, cancel it and create a new one.
  • The quantity is fixed. If your tokens fall below the quantity of a SELL order, the order changes to FAILED. This can occur before the mark price reaches the trigger price. Create a new order for the new size.
  • Halts and market hours. Stop and take orders do not trigger while trading is halted or the market is closed. When trading starts again, an order triggers immediately if the mark price is at or past its trigger price.
  • Competition rounds. When a trading competition round starts or ends, Loaf removes all stop and take orders on all properties. The status does not change to CANCELLED, and you do not get an update. Create your orders again after the change.
  • Open-order limits. PENDING and ARMED orders count toward your open-order limits, together with your other open orders.
  • Separate orders are not linked. A stop-loss and a take-profit that you create separately do not cancel each other. To get a linked pair, attach them to a buy.

Create a stop or take order

POST /api/orders/conditional

Creates a stop or take order for the authenticated user. The order starts with status ARMED.

Request body

Type: ConditionalOrderRequestBody (@loafmarkets/shared-types)

interface ConditionalOrderRequestBody { tokenName: string; // lowercase slug, e.g. "opera" — not the ticker price: number; // 0 for STOP_MARKET and TAKE_MARKET quantity: number; side: 'BUY' | 'SELL'; type: 'STOP_MARKET' | 'STOP_LIMIT' | 'TAKE_MARKET' | 'TAKE_LIMIT'; triggerPrice: number; timeInForce: 'GTC'; deadline: number; // always 0 }

Request

import axios from 'axios'; import type { ConditionalOrderRequestBody } from '@loafmarkets/shared-types'; // Stop-loss: sell 5 tokens if the mark price falls to 90 const body: ConditionalOrderRequestBody = { tokenName: 'opera', price: 0, quantity: 5, side: 'SELL', type: 'STOP_MARKET', triggerPrice: 90, timeInForce: 'GTC', deadline: 0, }; const response = await axios.post('https://api.loafmarkets.com/api/orders/conditional', body, { headers: { Authorization: `Bearer ${process.env.LOAF_API_KEY}` }, }); console.log(response.data);

Parameters

ParameterTypeRequiredDescription
tokenNamestringYes(body) Lowercase property tokenName (letters a-z only, max 20). Not the ticker.
pricenumberYes(body) Limit price in USDC for STOP_LIMIT and TAKE_LIMIT (max 2 decimal places). Use 0 for STOP_MARKET and TAKE_MARKET.
quantitynumberYes(body) Order size in tokens (max 1 decimal place).
side'BUY' | 'SELL'Yes(body) Order side. The side and the type set the direction. See Order types.
type'STOP_MARKET' | 'STOP_LIMIT' | 'TAKE_MARKET' | 'TAKE_LIMIT'Yes(body) Order type.
triggerPricenumberYes(body) Mark price in USDC that triggers the order (max 2 decimal places).
timeInForce'GTC'Yes(body) Time in force (always GTC).
deadlinenumberYes(body) Always 0.

Loaf refuses a trigger price that the mark price has already reached. A trigger price equal to the mark price is also refused. Before you send the order, compare the trigger price with the mark price from markprice:{tokenName}. If the mark price is already there, place a normal order.

When you create the order, Loaf also checks that:

  • For a SELL, you hold at least quantity tokens of the property. Tokens that are locked in your open orders count.
  • For a BUY, you have enough USDC for price × quantity plus the taker fee. USDC that is locked in your open orders counts.
  • The order value (price × quantity) is at least 10 USDC. A SELL of your whole position is exempt.
  • You are below your open-order limits.

For STOP_MARKET and TAKE_MARKET, these checks use the protected price, not the trigger price. Loaf checks your balance again when the order triggers.

Response

Type: OrderApiResponse

interface OrderApiResponse { success: boolean; orderId: number; errorMessage?: string; }

Example response:

{ "success": true, "orderId": 9101 }

orderId is the id of the stop or take order. Use it to cancel the order and to find the order in your order lists. Cancel order does not accept this id.

A 200 response means that the order is ARMED. It does not mean that the order will trigger or fill. If the order fails when it triggers, you get the reason in rejectionReason, not as an error from this endpoint. See Track your stop and take orders.

Errors

StatusCodeDescription
400validation_errorRequest failed schema validation. For example, price is not 0 for a STOP_MARKET order, or a price has more than 2 decimal places.
400—Order refused. The mark price already reached the trigger price, you do not have enough tokens or USDC, the order value is below the minimum, or you reached an open-order limit. The error message gives the reason.
401unauthorizedMissing or invalid authentication.
403—Trading closed, halted, ineligible for competition, or world gate rejected the property.
404not_foundUnknown tokenName.
500internal_errorUnexpected server error.

If the mark price already reached the trigger price, the message is Order would trigger immediately: the mark price is already through the trigger price.

Requires API key authentication.


Attach take-profit and stop-loss to a buy

POST /api/orders/

Add tpPrice, slPrice, or both to a BUY order. For the other fields, see Create order. Loaf creates a SELL for the full quantity of the buy:

FieldCreates
tpPriceA take-profit: TAKE_MARKET SELL with trigger price tpPrice
slPriceA stop-loss: STOP_MARKET SELL with trigger price slPrice

The two orders are linked. When one of them triggers, Loaf cancels the other one at the same time. This is the only way to get a linked pair.

Rules

  • You can attach them only to a BUY.
  • tpPrice must be above slPrice.
  • LIMIT buy: slPrice < price < tpPrice.
  • MARKET buy: slPrice < mark price < tpPrice. Loaf uses the mark price before slippage.
  • Prices can have at most 2 decimal places.
  • Do not send a field that you do not use. Do not send null.

Request

import axios from 'axios'; import type { OrderRequestBody } from '@loafmarkets/shared-types'; // Limit buy: 10 tokens @ 100, take-profit at 120, stop-loss at 90 const body: OrderRequestBody = { tokenName: 'opera', price: 100, quantity: 10, side: 'BUY', type: 'LIMIT', timeInForce: 'GTC', deadline: 0, tpPrice: 120, slPrice: 90, }; const response = await axios.post('https://api.loafmarkets.com/api/orders/', body, { headers: { Authorization: `Bearer ${process.env.LOAF_API_KEY}` }, }); console.log(response.data); // orderId is the id of the buy

What happens next

  1. Loaf creates the take-profit and the stop-loss with status PENDING. They cannot trigger yet.
  2. When the buy fills completely, both change to ARMED. This occurs a few seconds after the last fill.
  3. When one of them triggers, Loaf cancels the other one.

If the buy does not fill completely:

The buy endsThe take-profit and the stop-loss
With no fill (for example, you cancel it)Change to CANCELLED.
Partially filled (for example, you cancel the rest)Change to FAILED. Their quantity is larger than your position. Create new orders for the filled quantity.

While the buy is partially filled, the tokens that you already bought are not protected. The take-profit and the stop-loss stay PENDING until the buy fills completely.

The response contains only the orderId of the buy. To find the take-profit and the stop-loss, look for orders with parentOrderId equal to that orderId. They are in openOrders and in order_update messages. See Track your stop and take orders.

MARKET buy with tpPrice. Loaf checks tpPrice against the mark price before slippage, but the buy can fill above the mark price. If the buy fills at or above tpPrice, the take-profit can trigger as soon as it is armed and sell at a loss. The trigger price is not a minimum sale price, and it does not include fees. Set tpPrice well above the mark price, or use a LIMIT buy.

Errors

StatusCodeDescription
400validation_errortpPrice or slPrice breaks a rule above. For example, the order is a SELL, or slPrice is not below price.
400—MARKET buy: slPrice is not below the mark price, or tpPrice is not above it.

All other errors are the same as for Create order.


Cancel a stop or take order

POST /api/orders/conditional/cancel

Cancels one stop or take order with status PENDING or ARMED. This endpoint also works while trading is halted.

After the status changes to PLACED, the order on the book is a normal limit order. To cancel it, send its placedOrderId to Cancel order.

Request body

Type: CancelOrderRequestBody (@loafmarkets/shared-types)

interface CancelOrderRequestBody { orderId: number; // id of the stop or take order }

Request

import axios from 'axios'; import type { CancelOrderRequestBody } from '@loafmarkets/shared-types'; const body: CancelOrderRequestBody = { orderId: 9101, }; const response = await axios.post('https://api.loafmarkets.com/api/orders/conditional/cancel', body, { headers: { Authorization: `Bearer ${process.env.LOAF_API_KEY}` }, }); console.log(response.data);

Parameters

ParameterTypeRequiredDescription
orderIdnumberYes(body) Id of the stop or take order.

Response

Type: CancelOrderResponse

interface CancelOrderResponse { success: boolean; orderId: number; errorMessage?: string; }

Example response:

{ "success": true, "orderId": 9101 }

Errors

StatusCodeDescription
400validation_errorRequest failed schema validation.
400—The order is already PLACED, FAILED, or CANCELLED.
401unauthorizedMissing or invalid authentication.
404not_foundNo stop or take order with this id on your account. The id of a normal order also gives 404.
500internal_errorUnexpected server error.

Requires API key authentication. Cancel all orders also cancels your PENDING and ARMED stop and take orders. During a platform-wide trading halt, Cancel order and Cancel all orders return 403, but this endpoint still works. Use it to stop an order from triggering when trading starts again.


Track your stop and take orders

Stop and take orders are in the same lists as your other orders:

WhereWhat you get
openOrders in Portfolio componentStop and take orders with status PENDING or ARMED
Order historyStop and take orders with all statuses
order_update on your private portfolio:{userId} channelOne message for each status change

Active orders and Cancelled orders do not include stop and take orders.

Use type to find stop and take orders in these lists. A stop or take order has one of the four types above. It does not have the filledQuantity and filledAt fields.

Type: ConditionalOrderHistoryItem (@loafmarkets/shared-types)

interface ConditionalOrderHistoryItem { id: number; // id of the stop or take order propertyId: number; tokenName: string; side: 'BUY' | 'SELL'; type: 'STOP_MARKET' | 'STOP_LIMIT' | 'TAKE_MARKET' | 'TAKE_LIMIT'; status: 'PENDING' | 'ARMED' | 'PLACED' | 'FAILED' | 'CANCELLED'; timeInForce: 'GTC'; quantity: number; price: number; // price of the limit order that Loaf places triggerPrice: number; rejectionReason: string | null; deadline: number; parentOrderId: number | null; // the buy that a take-profit or stop-loss is attached to placedOrderId: number | null; // the limit order that Loaf placed triggeredAt: number | null; // unix seconds cancelledAt: number | null; // unix seconds createdAt: number; // unix seconds }

Example order_update message:

{ "type": "order_update", "order": { "id": 9101, "propertyId": 42, "tokenName": "opera", "side": "SELL", "type": "STOP_MARKET", "status": "PLACED", "timeInForce": "GTC", "quantity": 5, "price": 87.3, "triggerPrice": 90, "rejectionReason": null, "deadline": 0, "parentOrderId": null, "placedOrderId": 9150, "triggeredAt": 1758600000, "cancelledAt": null, "createdAt": 1758500000 }, "timestamp": 1758600000 }
import { isConditionalOrder, type OrderHistoryItem } from '@loafmarkets/shared-types'; function onOrderUpdate(order: OrderHistoryItem) { if (isConditionalOrder(order)) { console.log(order.type, order.status, order.triggerPrice, order.rejectionReason); } else { console.log(order.id, order.status, order.filledQuantity); } }
Last updated on