Getting Started

The ServaloDesk API is organized around REST. All requests accept JSON-encoded bodies and return JSON-encoded responses. Authentication is handled via JWT bearer tokens or API keys.

Base URL
https://api.servalodesk.app/v1
Authentication
Pass your token in the Authorization header:
Authorization: Bearer <your_token>
API Keys
Generate API keys from your dashboard under Settings → API. Use API keys for server-to-server integrations. JWT tokens are for client-side use and expire after 1 hour.
Rate Limits

Rate limits are applied per API key per minute. Exceeding the limit returns 429 Too Many Requests.

PlanRequests/minBurst
Starter ($29/mo)100200
Pro ($49/mo)5001,000
Studio ($79/mo)2,0004,000
Errors

ServaloDesk uses standard HTTP status codes. All errors return a JSON body with error and message fields.

CodeMeaning
400Bad Request — invalid parameters
401Unauthorized — missing or invalid token
403Forbidden — insufficient permissions
404Not Found
409Conflict — e.g. scheduling overlap
422Unprocessable — validation failed
429Rate limit exceeded
500Server error
Webhooks

ServaloDesk sends webhook events to your server for async actions. Configure your endpoint URL in Settings → Webhooks.

Signature verification
Every request includes a X-ServaloDesk-Signature header. Verify it with:
HMAC-SHA256(rawBody, webhookSecret)

Supported events: appointment.confirmed, appointment.cancelled, deposit.paid, deposit.refunded, consent_form.signed, booking_request.received, waitlist.slot_offered

Authentication
Request Body
ParamTypeDescription
emailstringArtist email address required
passwordstringMin 8 characters required
full_namestringDisplay name required
studio_namestringCreates a new tenant optional
Response
200 OK
{
  "user": {
    "id": "usr_9f3e2a1b",
    "email": "[email protected]",
    "full_name": "Kai Rodriguez",
    "role": "owner"
  },
  "token": "eyJhbGciOiJIUzI1NiJ9...",
  "tenant_id": "ten_8d2c1f0e"
}
Request Body
ParamTypeDescription
emailstringrequired
passwordstringrequired
Response
200 OK
{
  "token": "eyJhbGciOiJIUzI1NiJ9...",
  "refresh_token": "rt_a1b2c3d4e5f6",
  "expires_in": 3600,
  "user": { "id": "usr_9f3e2a1b", "email": "[email protected]" }
}
Request Body
ParamTypeDescription
emailstringrequired
Response
200 OK
{ "message": "Magic link sent to [email protected]" }
Studio
Response
200 OK
{
  "id": "ten_8d2c1f0e",
  "name": "Midnight Ink Studio",
  "slug": "midnight-ink",
  "plan": "pro",
  "trial_ends_at": null,
  "artists_count": 3,
  "created_at": "2024-01-15T10:00:00Z"
}
Query Params
ParamTypeDescription
periodstringweek | month | year (default: month) optional
Response
200 OK
{
  "appointments": { "total": 48, "confirmed": 32, "pending": 8, "completed": 6 },
  "revenue": { "this_month": 4200, "last_month": 3800, "deposits_pending": 320 },
  "clients": { "total": 142, "new_this_month": 12 },
  "no_show_rate": 0.04
}
Artists
200 OK
{
  "data": [
    {
      "id": "art_a1b2c3",
      "full_name": "Kai Rodriguez",
      "role": "owner",
      "styles": ["traditional", "neo-traditional", "blackwork"],
      "booking_link_slug": "kai-rodriguez",
      "is_accepting_bookings": true
    }
  ],
  "total": 3
}
Query Params
ParamTypeDescription
start_datestringISO 8601 date required
end_datestringISO 8601 date required
durationintegerMinutes needed (default: 120) optional
Response
200 OK
{
  "slots": [
    { "start": "2024-06-28T10:00:00Z", "end": "2024-06-28T12:00:00Z", "available": true },
    { "start": "2024-06-28T13:00:00Z", "end": "2024-06-28T15:00:00Z", "available": true }
  ]
}
200 OK
{
  "display_name": "Kai Rodriguez",
  "instagram_handle": "kai.ink",
  "styles": ["traditional", "neo-traditional"],
  "is_accepting_bookings": true,
  "availability_note": "Booking 8 weeks out",
  "min_deposit_percent": 30,
  "portfolio_images": ["https://cdn.servalodesk.app/..."]
}
Clients
Query Params
ParamTypeDescription
qstringSearch by name, email, or phone optional
artist_idstringFilter by preferred artist optional
limitintegerPage size (default: 20, max: 100) optional
cursorstringPagination cursor optional
Response
200 OK
{
  "data": [
    {
      "id": "cli_x9y8z7",
      "first_name": "Jasmine",
      "last_name": "Torres",
      "email": "[email protected]",
      "phone": "+15125550142",
      "total_spent": 847,
      "appointment_count": 3,
      "last_appointment_at": "2024-05-10T14:00:00Z"
    }
  ],
  "next_cursor": "eyJpZCI6ImNsaV94OXk4ejcifQ==",
  "has_more": true,
  "total": 142
}
Request Body
ParamTypeDescription
first_namestringrequired
last_namestringrequired
emailstringrequired
phonestringoptional
allergiesstringoptional
medical_notesstringoptional
preferred_artist_idstringoptional
Appointments
Request Body
ParamTypeDescription
client_idstringrequired
artist_idstringrequired
start_timedatetimeISO 8601 required
duration_minutesintegerrequired
tattoo_descriptionstringoptional
stylestringoptional
quoted_pricenumberIn USD optional
deposit_amountnumberOverrides % calc optional
Response
201 Created
{
  "id": "apt_d4e5f6a7",
  "status": "confirmed",
  "start_time": "2024-06-28T14:00:00Z",
  "end_time": "2024-06-28T17:00:00Z",
  "deposit_amount": 120,
  "deposit_paid": false,
  "client": { "id": "cli_x9y8z7", "full_name": "Jasmine Torres" },
  "artist": { "id": "art_a1b2c3", "full_name": "Kai Rodriguez" }
}

Confirms the appointment and automatically sends a deposit request email to the client. Creates a pending deposit record.

Request Body
ParamTypeDescription
send_deposit_requestbooleanDefault: true optional
deposit_due_datedatetimeDefault: 48hrs from now optional
Response
200 OK
{
  "appointment": { "id": "apt_d4e5f6a7", "status": "confirmed" },
  "deposit": { "id": "dep_b8c9d0e1", "amount": 120, "status": "pending", "due_date": "2024-06-14T10:00:00Z" },
  "notification_sent": true
}

Marks the appointment as no_show and forfeits any paid deposit. Client is notified. An audit log entry is created.

Booking Requests
Request Body
ParamTypeDescription
artist_slugstringrequired
client_namestringrequired
client_emailstringrequired
client_phonestringoptional
tattoo_descriptionstringrequired
stylestringoptional
body_placementstringoptional
size_estimatestringsmall | medium | large | full_piece optional
preferred_datesarrayUp to 5 ISO date strings optional
budgetstringBudget range optional
Response
201 Created
{
  "id": "req_f1g2h3i4",
  "status": "pending",
  "message": "Your request has been submitted. Kai will respond within 24-48 hours."
}
Request Body
ParamTypeDescription
start_timedatetimerequired
duration_minutesintegerrequired
quoted_pricenumberoptional
notesstringMessage to client optional
Deposits
Request Body
ParamTypeDescription
appointment_idstringrequired
success_urlstringRedirect after payment optional
cancel_urlstringRedirect on cancel optional
Response
200 OK
{
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_test_...",
  "session_id": "cs_test_a1b2c3d4e5",
  "amount": 12000,
  "currency": "usd",
  "expires_at": "2024-06-14T12:00:00Z"
}
Request Body
ParamTypeDescription
reasonstringartist_cancelled | client_request | other optional
amountnumberPartial refund amount. Defaults to full. optional
Response
200 OK
{
  "deposit": { "id": "dep_b8c9d0e1", "status": "refunded", "refunded_at": "2024-06-12T09:00:00Z" },
  "stripe_refund_id": "re_3NpT8K2eZvKYlo2C1QjEz",
  "timeline": "5-10 business days"
}
Waitlist
Request Body
ParamTypeDescription
available_datedatetimerequired
messagestringCustom note to client optional
expires_in_hoursintegerOffer expiry (default: 24) optional
Webhooks

Verified via Stripe-Signature header using your webhook signing secret. Handles:

EventAction
checkout.session.completedMark deposit paid, send confirmation
customer.subscription.createdActivate tenant account
customer.subscription.updatedUpdate plan tier
customer.subscription.deletedDowngrade to free / cancel
invoice.payment_failedMark past-due, trigger dunning email
charge.refundedUpdate deposit status to refunded
Changelog
v1.0.0 — June 2024 (Initial Release)
Full CRUD for studios, artists, clients, appointments, deposits, consent forms, and waitlist. Stripe webhook integration. Public booking request endpoint. Magic-link authentication.