SLO API
Build against the canonical SLO REST API, then use the agent endpoint when tool-call semantics are a better fit. Both surfaces share the same underlying application logic.
What can you do with it?
The REST API under /api/* is the long-term contract for shop automation and integrations. The agent endpoint is an adapter for tool-based clients, not a separate business-logic surface.
Products
Create, update, and remove products. Set prices, descriptions, and inventory — all at once or one at a time.
Drops
Set up new drops with dates, channels, and products. Open and close them when you're ready.
Orders
See incoming orders, confirm or cancel them, update fulfillment status as you pack and deliver.
Inventory
Update stock levels globally or per channel. Keep availability in sync without manual entry.
Channels
List your sales channels — delivery, pickup, wholesale — and manage which ones are active.
Start with REST when you want stable resource-oriented contracts. Use the agent endpoint when you want the same capabilities exposed as tool calls.
Quick start
Get an API key
Shop admin → Apps → Create Key. Copy it — shown once.
Call the canonical REST API
curl -X GET 'https://slo.earth/api/channels?shopId=shop_123' \ -H "Authorization: Bearer sk_live_..."
Use the agent adapter when needed
curl -X POST https://slo.earth/api/agent \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"tool":"listProducts"}'Get structured data back
{
"ok": true,
"result": {
"products": [...],
"total": 12
}
}Endpoints
Canonical REST API GET/POST https://slo.earth/api/* Agent adapter POST https://slo.earth/api/agent Authorization: Bearer sk_live_...
REST example
GET /api/channels?shopId=shop_123
POST /api/channels
{
"action": "create",
"shopId": "shop_123",
"title": "Wholesale"
}Agent example
POST /api/agent
{
"tool": "createChannel",
"params": { "title": "Wholesale" }
}Mutation response
{
"success": true,
"requestId": "req_123",
"replayed": false
}Error
{
"error": "Forbidden",
"requestId": "req_123"
}Authentication
Use a bearer token in the Authorization header. Each key is scoped to one shop and can be revoked at any time. For mutating requests, you can also send Idempotency-Key and X-Request-Id.
Authorization: Bearer sk_live_your_key_here Idempotency-Key: optional-but-recommended-for-mutations X-Request-Id: optional-client-request-id
Tools
products
listProductsList products for your shop. Use the 'search' param to filter by name/variety when looking for specific products — avoids returning the full catalog.
Params
{
"tool": "listProducts",
"params": {}
}→ { products: Product[], total: number }
getProductGet a single product by ID
Params
{
"tool": "getProduct",
"params": {
"productId": "product_abc123"
}
}→ { product: Product }
createProductCreate a new product
Params
{
"tool": "createProduct",
"params": {
"title": "Organic Tomatoes"
}
}→ { product: Product }
updateProductUpdate an existing product. Only send fields you want to change.
Params
{
"tool": "updateProduct",
"params": {
"productId": "product_abc123"
}
}→ { product: Product }
batchUpdateProductsUpdate multiple products at once. Send a JSON array of updates as a string.
Params
{
"tool": "batchUpdateProducts",
"params": {
"updates": "..."
}
}→ { results: { productId, ok, title?, error? }[] }
deleteProductDelete a product
Params
{
"tool": "deleteProduct",
"params": {
"productId": "product_abc123"
}
}→ { deleted: true }
uploadProductImageSet a product's primary image. IMPORTANT: When the user has attached an image in chat, ALWAYS use assetId from the attachment metadata — NEVER use imageBase64. The assetId is shown in the attachment info like 'Sanity asset ID: image-xxx'. Only use imageUrl for external URLs. Never generate or fabricate base64 data.
Params
{
"tool": "uploadProductImage",
"params": {
"productId": "product_abc123"
}
}→ { success: boolean, assetRef: string, productId: string }
inventory
updateInventoryUpdate inventory for a product (global or per-channel)
Params
{
"tool": "updateInventory",
"params": {
"productId": "product_abc123"
}
}→ { product: Product }
batchUpdateInventoryUpdate inventory for multiple products in one call. More efficient than calling updateInventory multiple times.
Params
{
"tool": "batchUpdateInventory",
"params": {
"updates": "..."
}
}→ { results: { productId, title, variety, previousAvailable, newAvailable, unit, imageUrl }[] }
drops
listDropsList drops for your shop
Params
{
"tool": "listDrops",
"params": {}
}→ { drops: Drop[] }
getDropGet a single drop by ID
Params
{
"tool": "getDrop",
"params": {
"dropId": "drop_abc123"
}
}→ { drop: Drop }
createDropCreate a new drop (scheduled product release). Only channelIds + deliveryStart are required. Missing dates get smart defaults at publish: dropStart=now, dropEnd=deliveryStart-3h, deliveryEnd=deliveryStart+3h. If deliveryStart is provided, auto-publishes.
Params
{
"tool": "createDrop",
"params": {
"channelIds": [
"channel_abc123"
]
}
}→ { drop: Drop }
updateDropUpdate an existing drop. Only send fields you want to change.
Params
{
"tool": "updateDrop",
"params": {
"dropId": "drop_abc123"
}
}→ { drop: Drop }
deleteDropDelete a drop. Fails if the drop has associated orders.
Params
{
"tool": "deleteDrop",
"params": {
"dropId": "drop_abc123"
}
}→ { deleted: true }
addProductsToDropAdd products to an existing drop (appends, does not replace)
Params
{
"tool": "addProductsToDrop",
"params": {
"dropId": "drop_abc123",
"products": []
}
}→ { drop: Drop }
removeProductFromDropRemove a product from a drop by its _key
Params
{
"tool": "removeProductFromDrop",
"params": {
"dropId": "drop_abc123",
"productKey": "..."
}
}→ { drop: Drop }
publishDropPublish a draft drop (makes it visible to buyers). Only deliveryStart is required — missing dates get smart defaults: dropStart=now, dropEnd=deliveryStart-3h, deliveryEnd=deliveryStart+3h.
Params
{
"tool": "publishDrop",
"params": {
"dropId": "drop_abc123"
}
}→ { drop: Drop }
setDropDraftSet a drop back to draft (hides from buyers)
Params
{
"tool": "setDropDraft",
"params": {
"dropId": "drop_abc123"
}
}→ { drop: Drop }
orders
listOrdersList orders for your shop
Params
{
"tool": "listOrders",
"params": {}
}→ { orders: Order[], total: number }
getOrderGet a single order by _id or order number
Params
{
"tool": "getOrder",
"params": {
"orderId": "order_abc123"
}
}→ { order: Order }
updateOrderStatusUpdate the status of an order
Params
{
"tool": "updateOrderStatus",
"params": {
"orderId": "order_abc123",
"status": "active"
}
}→ { order: Order }
confirmOrderConfirm a pending order. Sets correct status based on payment type (invoice → confirmedInvoice, swish → paid), sets fulfillment to packing, and notifies the buyer.
Params
{
"tool": "confirmOrder",
"params": {
"orderId": "order_abc123"
}
}→ { order: Order }
cancelOrderCancel an order. Refunds inventory and notifies the buyer.
Params
{
"tool": "cancelOrder",
"params": {
"orderId": "order_abc123"
}
}→ { success: true }
updateFulfillmentUpdate order fulfillment status. Valid statuses: packing, ready, inTransit, delivered. Notifies the buyer on status change.
Params
{
"tool": "updateFulfillment",
"params": {
"orderId": "order_abc123",
"status": "active"
}
}→ { order: Order }
editOrderItemsEdit item prices/quantities and/or remove items from an existing order. Quantities are in display units. Inventory is auto-adjusted for pending orders.
Params
{
"tool": "editOrderItems",
"params": {
"orderId": "order_abc123"
}
}→ { order: Order }
addOrderItemsAdd one or more products to an existing order. Quantities in display units (kg/L/count). Inventory is auto-adjusted.
Params
{
"tool": "addOrderItems",
"params": {
"orderId": "order_abc123",
"items": "..."
}
}→ { order: Order }
addOrderItemsFromCartAppend one or more pre-built order items to an existing order. Use this when the items were already constructed by the cart builder.
Params
{
"tool": "addOrderItemsFromCart",
"params": {
"orderId": "order_abc123",
"items": []
}
}→ { order: Order }
updateDeliveryPriceUpdate the delivery/pickup price on an existing order. Pass the new ex-VAT price. Tax summary auto-recalculates.
Params
{
"tool": "updateDeliveryPrice",
"params": {
"orderId": "order_abc123",
"deliveryPrice": 10
}
}→ { order: Order }
reassignOrderDropMove an order to a different drop. Updates delivery dates and resets fulfillment. Prices and items stay the same. The new drop must have the same channel as the order.
Params
{
"tool": "reassignOrderDrop",
"params": {
"orderId": "order_abc123",
"dropId": "drop_abc123"
}
}→ { success: true }
capturePaymentCapture a Stripe payment for an uncaptured order, mark it paid, and move fulfillment to packing.
Params
{
"tool": "capturePayment",
"params": {
"orderId": "order_abc123"
}
}→ { order: Order }
cancelPaymentCancel a Stripe payment and mark the order as cancelled.
Params
{
"tool": "cancelPayment",
"params": {
"orderId": "order_abc123"
}
}→ { success: true }
createOrderCreate an order. Known buyers (entityId) get a finalized order immediately (inventory overdraft is allowed — the seller already confirmed via estimate). Unknown buyers (email only) get a draft with an invitation. If finalization fails, the order is saved as a visible draft for admin review.
Params
{
"tool": "createOrder",
"params": {
"channelId": "channel_abc123",
"dropId": "drop_abc123",
"items": "..."
}
}→ { order: { orderId, orderNumber, status }, invitation?: { invitationId } }
estimateOrderCompute order totals (items + delivery + grand total) without creating an order. Use this to show a breakdown before confirming. Returns stock availability per item — check hasOverstock and warn the user about insufficient inventory before proceeding.
Params
{
"tool": "estimateOrder",
"params": {
"channelId": "channel_abc123",
"dropId": "drop_abc123",
"items": "..."
}
}→ { estimate: { buyerName, channel, items: [{ ..., availableStock, requestedStock, overstock }], subtotal, delivery, deliveryOverridden, grandTotal, currency, taxType, hasOverstock } }
channels
listChannelsList all channels for your shop
{
"tool": "listChannels"
}→ { channels: Channel[] }
getChannelGet a single channel by ID
Params
{
"tool": "getChannel",
"params": {
"channelId": "channel_abc123"
}
}→ { channel: Channel }
createChannelCreate a new sales channel
Params
{
"tool": "createChannel",
"params": {
"title": "Organic Tomatoes"
}
}→ { channel: Channel }
updateChannelUpdate a channel. Only send fields you want to change.
Params
{
"tool": "updateChannel",
"params": {
"channelId": "channel_abc123"
}
}→ { channel: Channel }
deleteChannelDelete a channel. Fails if drops reference this channel.
Params
{
"tool": "deleteChannel",
"params": {
"channelId": "channel_abc123"
}
}→ { deleted: true }
shop
getShopGet your shop details
{
"tool": "getShop"
}→ { shop: Shop }
updateShopProfileUpdate the shop's public profile. Only send fields you want to change: title, description, contact info, social links, certifications.
Params
{
"tool": "updateShopProfile",
"params": {}
}→ { success: true }
updateShopLocalisationUpdate shop localisation settings: language, country, currency, tax rate and mode. Only send fields you want to change.
Params
{
"tool": "updateShopLocalisation",
"params": {}
}→ { success: true, language?: string }
updateShopSlugChange the shop's URL handle (slo.earth/<handle>). Must be unique, lowercase letters/numbers/hyphens only.
Params
{
"tool": "updateShopSlug",
"params": {
"slug": "..."
}
}→ { success: true, slug: string }
togglePickupEnable or disable pickup at the shop's physical address.
Params
{
"tool": "togglePickup",
"params": {
"enabled": false
}
}→ { success: true, pickupEnabled: boolean }
updatePhysicalStoreConfigure the shop's physical store: opening hours, pickup days, and Google sync. Opening hours is an array of 7 objects (day 0=Monday to 6=Sunday) with open/close times and closed flag.
Params
{
"tool": "updatePhysicalStore",
"params": {
"enabled": false,
"openingHours": []
}
}→ { success: true }
toggleVacationModeEnable or disable vacation mode for the shop. When on, the shop appears as on vacation to buyers. Optionally set start and end dates.
Params
{
"tool": "toggleVacationMode",
"params": {
"vacationMode": false
}
}→ { success: true, vacationMode: boolean }
updateShopColorsUpdate the shop's brand colors. Only send colors you want to change. Values should be valid CSS color values (hex, rgb, etc.).
Params
{
"tool": "updateShopColors",
"params": {}
}→ { success: true }
updateSwishConfigConfigure Swish payment (Swedish mobile payment). Set the Swish phone number and company name.
Params
{
"tool": "updateSwishConfig",
"params": {
"phone": "...",
"company": "..."
}
}→ { success: true }
publishShopPublish or unpublish the shop
Params
{
"tool": "publishShop",
"params": {
"publish": false
}
}→ { shop: Shop }
customers
listCustomersList customers (buyers/followers) for your shop. Returns company name and channel membership for each buyer. Searches entity title, org name, and company name.
Params
{
"tool": "listCustomers",
"params": {}
}→ { customers: Customer[] } // Each: { id, displayName, companyName, email, channels: [{_id, title}] }
updateFollowerChannelsUpdate which channels a buyer has access to
Params
{
"tool": "updateFollowerChannels",
"params": {
"entityId": "...",
"channelIds": [
"channel_abc123"
]
}
}→ { updated: true }
inviteBuyerInvite a buyer to follow your shop. ONLY call this after: 1) confirming the buyer is not already a follower (listCustomers), 2) the admin has provided the email, 3) you have read the email back and the admin confirmed it.
Params
{
"tool": "inviteBuyer",
"params": {
"email": "..."
}
}→ { invitation: { invitationId } }
notifications
notifyFollowersSend a push notification to all shop followers
Params
{
"tool": "notifyFollowers",
"params": {
"title": "Organic Tomatoes",
"body": "..."
}
}→ { sent: true, count: number }
meta
listToolsList all available API tools and their parameters
{
"tool": "listTools"
}→ { tools: ToolDefinition[] }
reportTrainingFeedbackLog a training correction from the admin for the developer AI to process. Use when the admin corrects your response or teaches you a rule.
Params
{
"tool": "reportTrainingFeedback",
"params": {
"userMessage": "...",
"aiResponse": "...",
"correction": "..."
}
}→ { logged: true, id: string }
Errors
REST endpoints return HTTP status codes plus a stable JSON error shape. Mutations may include a requestId for tracing. The agent adapter returns the same underlying failures mapped into tool-call responses.
unauthorizedInvalid or revoked API key
forbiddenKey doesn't have permission for this tool
not_foundResource doesn't exist, or unknown tool name
validationMissing or invalid params
rate_limitToo many requests — 60/min per key
internalSomething went wrong on our end
Rate limits
60 requests per minute per key. Check the response headers:
X-RateLimit-Limit: 60 X-RateLimit-Remaining: 58 X-RateLimit-Reset: 1708617600
For agents
If you're an AI agent: call listTools first to discover every available operation and its parameter schema.
{ "tool": "listTools" }The response includes name, description, category, params, and return shape — everything you need.