Guides

Code examples

Copy-paste clients for curl, JavaScript, Python, PHP and C#.

All examples read QUILO_URL and QUILO_API_KEY from the environment.

curl#

Terminal
# read
curl "$QUILO_URL/api/v1/appointments?starts_after=2026-10-20T00:00:00Z&limit=20" \
  -H "Authorization: Bearer $QUILO_API_KEY"

# create
curl -X POST "$QUILO_URL/api/v1/appointments" \
  -H "Authorization: Bearer $QUILO_API_KEY" -H "Content-Type: application/json" \
  -d '{"brand_id":"0b7e9a52-…","title":"Ring fitting","starts_at":"2026-10-20T14:00:00+02:00","ends_at":"2026-10-20T14:45:00+02:00"}'

# change
curl -X PATCH "$QUILO_URL/api/v1/tasks/TASK_ID" \
  -H "Authorization: Bearer $QUILO_API_KEY" -H "Content-Type: application/json" \
  -d '{"status":"done"}'

JavaScript / TypeScript (Node 18+)#

TypeScript
type QuiloError = { error: { type: string; message: string; request_id: string; errors?: { field: string; message: string }[] } };

export class Quilo {
  constructor(private base = process.env.QUILO_URL!, private key = process.env.QUILO_API_KEY!) {}

  async request<T>(method: string, path: string, body?: unknown, idempotencyKey?: string): Promise<T> {
    for (let attempt = 0; ; attempt++) {
      const res = await fetch(`${this.base}/api/v1${path}`, {
        method,
        headers: {
          Authorization: `Bearer ${this.key}`,
          ...(body ? { 'Content-Type': 'application/json' } : {}),
          ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
        },
        body: body ? JSON.stringify(body) : undefined,
      });
      if ((res.status === 429 || res.status >= 500) && attempt < 4) {
        await new Promise((r) => setTimeout(r, 1000 * Number(res.headers.get('retry-after') ?? 2 ** attempt)));
        continue;
      }
      const json = await res.json();
      if (!res.ok) throw new Error(`${(json as QuiloError).error.type}: ${(json as QuiloError).error.message} (${(json as QuiloError).error.request_id})`);
      return json as T;
    }
  }
  get = <T>(path: string) => this.request<T>('GET', path);
  create = <T>(resource: string, body: unknown, idem?: string) => this.request<T>('POST', `/${resource}`, body, idem);
  update = <T>(resource: string, id: string, body: unknown) => this.request<T>('PATCH', `/${resource}/${id}`, body);
  remove = (resource: string, id: string) => this.request('DELETE', `/${resource}/${id}`);
}

// const q = new Quilo();
// const me = await q.get('/me');
// const task = await q.create('tasks', { brand_id, title: 'Call Elena' }, 'task-elena-1');

Python 3#

Python
import os, time, requests

class Quilo:
    def __init__(self, base=os.environ.get("QUILO_URL"), key=os.environ.get("QUILO_API_KEY")):
        self.base = f"{base}/api/v1"
        self.s = requests.Session()
        self.s.headers["Authorization"] = f"Bearer {key}"

    def request(self, method, path, json=None, idempotency_key=None, params=None):
        headers = {"Idempotency-Key": idempotency_key} if idempotency_key else {}
        for attempt in range(5):
            r = self.s.request(method, self.base + path, json=json, params=params, headers=headers, timeout=30)
            if r.status_code in (429, 500, 502, 503, 504) and attempt < 4:
                time.sleep(int(r.headers.get("Retry-After", 2 ** attempt)))
                continue
            if r.status_code >= 400:
                e = r.json()["error"]
                raise RuntimeError(f"{e['type']}: {e['message']} ({e['request_id']})")
            return r.json()

    def list_all(self, resource, **filters):
        cursor = None
        while True:
            page = self.request("GET", f"/{resource}", params={**filters, "limit": 200, **({"cursor": cursor} if cursor else {})})
            yield from page["data"]
            if not page["has_more"]:
                return
            cursor = page["next_cursor"]

# q = Quilo()
# for c in q.list_all("clients", updated_since="2026-10-01T00:00:00Z"): print(c["email"])

PHP#

See the helper in the website form guide.

C# (.NET 8)#

C#
using System.Net.Http.Headers;
using System.Net.Http.Json;

var http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable("QUILO_URL") + "/api/v1/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("QUILO_API_KEY"));

var me = await http.GetFromJsonAsync<System.Text.Json.JsonElement>("me");
var req = new HttpRequestMessage(HttpMethod.Post, "tasks") { Content = JsonContent.Create(new { brand_id = "0b7e9a52-…", title = "Call Elena" }) };
req.Headers.Add("Idempotency-Key", "task-elena-1");
var res = await http.SendAsync(req);
Console.WriteLine($"{(int)res.StatusCode} {await res.Content.ReadAsStringAsync()}");

OpenAPI#

The machine-readable description is at $QUILO_URL/api/v1/openapi.json (no key needed). Import it into Postman, Insomnia, Bruno or an OpenAPI code generator to get a typed client in your language.