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.
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 }[] }
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 }
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 walk-in opening hours and Google sync. Opening hours is display-only metadata (the farm-shop walk-in story); online ordering windows are configured separately via order schedules. 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 }
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 }
createOrderScheduleCreate an order schedule (producer-facing: 'schedule'). Restricts when customers can order and which delivery methods / pickup options apply. Multiple schedules coexist for parallel sales streams. Provide a TimeWindow for `timeWindow` (when buyers can order) and `deliveryTimeWindow` (when fulfillment happens).
Params
{
"tool": "createOrderSchedule",
"params": {}
}→ { orderSchedule: OrderSchedule }
updateOrderScheduleUpdate an order schedule. Only include the fields you want to change. Arrays (channelIds, deliveryMethodIds, pickupOptionIds) replace the stored list when provided. timeWindow / deliveryTimeWindow are full TimeWindow objects.
Params
{
"tool": "updateOrderSchedule",
"params": {
"orderScheduleId": "..."
}
}→ { orderSchedule: OrderSchedule }
delivery
createDeliveryMethodCreate a shop-global delivery method (delivery area + schedule + pricing). Drops filter which of these methods' slots to offer — they do not own delivery config.
Params
{
"tool": "createDeliveryMethod",
"params": {
"title": "Organic Tomatoes",
"geoAreaLabel": "..."
}
}→ { deliveryMethod: DeliveryMethod }
updateDeliveryMethodUpdate a shop-global delivery method. Only send fields you want to change.
Params
{
"tool": "updateDeliveryMethod",
"params": {
"deliveryMethodId": "..."
}
}→ { deliveryMethod: DeliveryMethod }
archiveDeliveryMethodSoft-delete a delivery method (archived = true). Existing slots and historical orders keep resolving; the method is hidden from new bookings.
Params
{
"tool": "archiveDeliveryMethod",
"params": {
"deliveryMethodId": "..."
}
}→ { archived: true }
pickup
createPickupOptionCreate a shop-global pickup option (location + schedule + pricing). Independent of shop.physicalStore. Drops filter which of these options' slots to offer.
Params
{
"tool": "createPickupOption",
"params": {
"title": "Organic Tomatoes",
"location": {}
}
}→ { pickupOption: PickupOption }
updatePickupOptionUpdate a shop-global pickup option. Only send fields you want to change.
Params
{
"tool": "updatePickupOption",
"params": {
"pickupOptionId": "..."
}
}→ { pickupOption: PickupOption }
archivePickupOptionSoft-delete a pickup option. Existing slots and historical orders keep resolving; hidden from new bookings.
Params
{
"tool": "archivePickupOption",
"params": {
"pickupOptionId": "..."
}
}→ { archived: true }
slots
addSlotExceptionAdd an exception to a delivery method's or pickup option's calendar on a specific date — 'skip' (hide a recurrence occurrence), 'added' (add an extra occurrence), or adjust capacityOverride / notes. Materializes the slot doc if needed.
Params
{
"tool": "addSlotException",
"params": {
"methodOrOptionId": "...",
"kind": "...",
"date": "..."
}
}→ { slot: Slot }
removeSlotExceptionRemove a slot exception. Fails if orders are already booked in the slot. If safe, the slot doc is deleted and any recurrence occurrence reappears automatically.
Params
{
"tool": "removeSlotException",
"params": {
"slotId": "..."
}
}→ { removed: true }
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.