API reference

All endpoints live under https://app.tikk.chat/api/v1, return JSON and require a Bearer token:

curl https://app.tikk.chat/api/v1/profile \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Accept: application/json"

Timestamps are ISO 8601. Everything is scoped to the Tikk user the token belongs to.

Token

GET /token works with any valid token and needs no scope.

Describes the token making the request: the scopes it holds, and whether each one works on the user's current plan.

{
  "plan": "free",
  "scopes": [
    { "id": "profile.read", "available": true },
    { "id": "bookings.read", "available": false, "reason": "plan_required" }
  ]
}

Check available rather than hard-coding which scopes need Pro. See Plans.

Profile

GET /profile requires profile.read.

Returns the user's public profile.

{
  "username": "jane",
  "name": "Jane Doe",
  "title": "Coach",
  "bio": "…",
  "avatar_url": "https://…",
  "logo_url": null,
  "profile_url": "https://tikk.chat/jane",
  "profile_complete": true,
  "plan": "pro"
}

profile_complete is false until the user has picked a username; profile_url is null until then. plan is free or pro.

Services

GET /services requires profile.read.

The services visitors can book right now: active ones, within the user's plan limit. The default service comes first. Single-use links are not services; they're under Invites.

GET /services/{slug} requires profile.read.

A single service. A slug that isn't bookable right now returns 404.

{
  "data": {
    "slug": "intro-call",
    "title": "Intro call",
    "description": "A first chat to see if we're a fit.",
    "is_default": true,
    "is_group": false,
    "max_attendees": null,
    "requires_approval": true,
    "durations": [
      { "minutes": 30, "price": null, "currency": "EUR" },
      { "minutes": 60, "price": 7500, "currency": "EUR" }
    ],
    "location": { "type": "video", "label": "Video Meeting", "address": null },
    "embed_url": "https://tikk.chat/jane/intro-call"
  }
}

price is in minor units (cents), and null for a free duration. requires_approval is true when the user accepts each booking by hand. embed_url is null until the user has picked a username; see Embedding booking pages for what to do with it.

Availability

GET /availability requires availability.read.

Free slots per day, computed exactly as the user's public booking page does, including their working hours, buffers and busy calendars.

Query parameter Default Notes
service none A service slug from /services. Unknown slugs return 404.
from today Date, e.g. 2026-10-01.
to from + 7 days At most 31 days after from.
duration the service's shortest duration, or 30 Minutes, 15 to 180.
{
  "data": {
    "2026-10-01": [
      { "start": "2026-10-01T08:00:00Z", "end": "2026-10-01T08:30:00Z", "label": "10:00" }
    ]
  },
  "meta": {
    "from": "2026-10-01",
    "to": "2026-10-08",
    "duration_minutes": 30,
    "timezone": "Europe/Brussels",
    "service": "intro-call"
  }
}

Days without free slots are left out. start and end are UTC; label is in the user's timezone. Group services also return spots_remaining per slot.

Bookings

GET /bookings requires bookings.read and a user on Tikk Pro.

The user's bookings, paginated.

Query parameter Default Notes
status all pending, accepted, declined or cancelled.
from, to none Filter on the scheduled time.
order desc asc or desc, by scheduled time.
per_page 25 At most 100.

The response has data (a list of bookings, shaped as below), plus links and meta for pagination.

GET /bookings/{id} requires bookings.read and a user on Tikk Pro.

A single booking. An id that doesn't belong to the user returns 404.

{
  "id": 48213,
  "status": "accepted",
  "topic": "Intro call",
  "duration_minutes": 30,
  "scheduled_at": "2026-10-01T08:00:00+00:00",
  "created_at": "2026-09-28T14:12:09+00:00",
  "notes": null,
  "cancellation_reason": null,
  "booker": { "name": "Sam Smith", "email": "[email protected]" },
  "attendees": [],
  "service": {
    "slug": "intro-call",
    "title": "Intro call",
    "is_group": false,
    "max_attendees": null,
    "is_personal_invite": false
  },
  "location": { "type": "google_meet", "label": "Google Meet", "address": null },
  "meeting_link": null,
  "conference_link": "https://meet.google.com/…",
  "payment": { "status": null, "amount": null, "currency": "EUR" }
}

payment.amount is in minor units (cents).

Invites

Single-use links: a private booking link for one person, spent once it's booked. Every booking made through one is accepted straight away and is never paid.

GET /invites requires invites.read.

The user's links, newest first, paginated. Takes per_page (default 25, at most 100). The response has data plus links and meta for pagination.

GET /invites/{id} requires invites.read.

{
  "data": {
    "id": 73920,
    "title": "Intro call for Sam",
    "description": null,
    "status": "open",
    "url": "https://tikk.chat/jane/Xk3pQ9aB2c/intro-call-for-sam",
    "contact": { "name": "Sam Smith", "email": "[email protected]" },
    "durations": [30],
    "expires_at": "2026-10-31T09:00:00+00:00",
    "created_at": "2026-10-01T09:00:00+00:00"
  }
}
status Meaning
open Can be booked.
used Booked; the link no longer works.
expired Past expires_at without a booking.
disabled Switched off by the user in Tikk.

POST /invites requires invites.write.

Creates a link and returns it with status 201. Send the person its url.

Field Notes
service Optional. A service slug to copy: the link offers that service's durations and location.
title Required without service, at most 50 characters. With service it replaces the default title, "{service} for {contact_name}".
description Optional, at most 500 characters.
contact_name, contact_email Optional. Who the link is for; saved to the user's contacts when an email is given.
expires_in_days 1 to 365, default 30.
duration Minutes, 5 to 480, default 30. Only without service.
curl -X POST https://app.tikk.chat/api/v1/invites \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"service": "intro-call", "contact_name": "Sam Smith", "contact_email": "[email protected]"}'

DELETE /invites/{id} requires invites.write.

Deletes an unbooked link and returns 204. A link that has been booked returns 409 and stays: it's the booking's record of how it was made.