Recipes

API recipes (customers, tasks, actions, webhooks) and how to model a barbershop, aesthetics, clinic, restaurant and deliveries.

Updated:

API recipes

All of them use $WAGEND_KEY as in the quickstart.

Create a customer and book for them in one go

Needs customers:write and bookings:write. The phone number is the customer's unique identifier: if it already exists, the API answers 409 customer_exists.

curl -X POST https://api.wagend.app/v1/customers \
  -H "Authorization: Bearer $WAGEND_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Marina", "phone": "+5521988887777", "locale": "pt", "tags": ["vip"],
        "address": { "street": "Rua das Flores", "number": "120", "city": "Rio de Janeiro", "formatted": "Rua das Flores 120, Rio de Janeiro" } }'

curl -X POST https://api.wagend.app/v1/bookings \
  -H "Authorization: Bearer $WAGEND_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "service_id": "svc_corte", "start": "2026-10-15T09:45:00-03:00",
        "customer": { "name": "Marina", "phone": "+5521988887777" } }'

POST /bookings does the hold and the confirmation in a single call.

Read what is due today

Needs bookings:read. GET /work-items is the same query the Today screen uses.

curl "https://api.wagend.app/v1/work-items?view=today&limit=50" \
  -H "Authorization: Bearer $WAGEND_KEY"

# Only what nobody has taken yet
curl "https://api.wagend.app/v1/work-items?unassigned=true&limit=50" \
  -H "Authorization: Bearer $WAGEND_KEY"

Each task carries its stage and its available_actions.

Assign and close a delivery with actions

Needs bookings:write. Actions are named by the business's stage configuration (with the deliveries template: atribuir, soltar, iniciar, concluir, nao_estava). Look them up in GET /stage-config or in the task's available_actions.

# Assign the delivery to a courier (resource_id is the courier's resource)
curl -X POST https://api.wagend.app/v1/bookings/$TASK_ID/actions/atribuir \
  -H "Authorization: Bearer $WAGEND_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "data": {}, "resource_id": "'$COURIER_ID'" }'

# Close it as completed
curl -X POST https://api.wagend.app/v1/bookings/$TASK_ID/actions/concluir \
  -H "Authorization: Bearer $WAGEND_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "data": {} }'

If two people take the same task at once, one wins and the other gets 409 already_claimed. The "Accept delivery" action (aceitar) is for staff from the dashboard or Telegram: it takes the task with the resource of whoever runs it.

Receive webhook notifications

Register an endpoint (see Webhooks) and verify the signature before processing. Minimal Express example:

import express from 'express'
import { verifyWagend } from './verify' // the function from the Webhooks page

const app = express()
app.post('/wagend', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8')
  if (!verifyWagend(raw, req.header('Wagend-Signature') ?? '', process.env.WAGEND_WEBHOOK_SECRET!)) {
    return res.sendStatus(400)
  }
  const event = JSON.parse(raw)
  if (event.type === 'booking.confirmed') console.log('New booking', event.data.booking.id)
  res.sendStatus(200) // answer 2xx quickly; duplicates are dropped by event.id
})
app.listen(3000)

Templates by business

Each recipe matches a ready-made template you can pick during onboarding.

Barbershop

Each barber is an exclusive staff resource with their own schedule. One group, customer_choice with a round_robin fallback.

{
  "name": "Haircut",
  "duration_min": 30,
  "step_min": 15,
  "buffer_after_min": 5,
  "price_cents": 5000,
  "requirements": [{ "resource_group_id": "grp_barbers", "units": 1 }]
}

Aesthetics

Professionals, machines and rooms are separate resources. A service requires all three at the same time, so the scarcest one (usually the machine) limits the agenda.

{
  "name": "Laser hair removal",
  "duration_min": 45,
  "buffer_after_min": 10,
  "price_cents": 18000,
  "deposit_cents": 5000,
  "requirements": [
    { "resource_group_id": "grp_professionals", "units": 1 },
    { "resource_group_id": "grp_lasers", "units": 1 },
    { "resource_group_id": "grp_rooms", "units": 1 }
  ]
}

Clinic

Doctor plus office, per location (one workspace per location). Health data is sensitive: the assistant never asks about symptoms, and the intake form collects only what scheduling needs.

{
  "name": "Appointment",
  "duration_min": 30,
  "buffer_after_min": 10,
  "intake_form": { "type": "object", "properties": { "insurance": { "type": "string" } } },
  "requirements": [
    { "resource_group_id": "grp_doctors", "units": 1 },
    { "resource_group_id": "grp_offices", "units": 1 }
  ]
}

Restaurant

The dining room is one pooled resource with 40 units (covers) per slot. Duration grows with the party size, and large parties require a deposit.

{
  "name": "Table",
  "step_min": 30,
  "duration_by_units": [
    { "max_units": 2, "duration_min": 90 },
    { "max_units": 4, "duration_min": 120 },
    { "max_units": 20, "duration_min": 150 }
  ],
  "requirements": [{ "resource_group_id": "grp_dining_room", "units": "party_size" }]
}

Table mode (specific tables with minimum and maximum size, and combinations) is on the roadmap.

Deliveries

Each zone is a pooled resource with a capacity per 2-hour window. The zone is selected by postal code, and a cutoff rule closes same-day windows at noon.

{
  "name": "Delivery",
  "window_mode": true,
  "duration_min": 120,
  "cutoff_rule": { "same_day_until": "12:00" },
  "intake_form": { "type": "object", "required": ["address", "postal_code"] },
  "requirements": [{ "resource_group_id": "grp_zones", "units": 1, "select_by": "postal_code" }]
}