Quickstart
The three CRM buttons end to end with curl — plan, invite, create a team member — and your first webhook.
This quickstart walks through the calls behind the three buttons, with curl, so you can see every request and answer before you write a line of your own code.
Before you start
- Have an admin create a key with only the five scopes these steps use:
services:read,users:read,planning_links:write,invitations:write, andusers:writewith one role. See Authentication. - Put the key in your shell:
export SCHEDULINQ_API_KEY=sq_live_...
1. Check your key
List your services. A 200 means the key works; you need a service's id later on.
Example request · GET /api/v1/services
curl "https://api.schedulinq.com/api/v1/services?limit=25" \
-H "Authorization: Bearer $SCHEDULINQ_API_KEY"Response · 200
{
"data": [
{
"colleagueIds": [
"3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e03"
],
"createdAt": "2026-09-01T08:00:00Z",
"durationMinutes": 60,
"id": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e01",
"identifier": "offertebezoek",
"name": "Offertebezoek",
"names": {
"en": "Quote visit",
"nl": "Offertebezoek"
}
}
],
"hasMore": false,
"nextCursor": "MjAyNi0wOS0wMVQwODowMDowMFp8M2YwYzhhNTItNmIxZS00ZDdhLTljM2UtMWEyYjNjNGQ1ZTAx"
}2. Find the team member
List your team members, and pick the one the work is for.
Example request · GET /api/v1/users
curl "https://api.schedulinq.com/api/v1/users?limit=25" \
-H "Authorization: Bearer $SCHEDULINQ_API_KEY"Response · 200
{
"data": [
{
"admin": false,
"calendarConnected": true,
"createdAt": "2026-10-01T08:30:00Z",
"customFields": {
"bedrijf": "Installatiebedrijf Jansen"
},
"email": "sanne@installatiebedrijf-jansen.example",
"firstName": "Sanne",
"id": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e03",
"invitationExpiresAt": "2026-10-08T08:30:00Z",
"invitationId": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e05",
"lastName": "de Vries",
"metadata": {
"campaignId": "C-42"
},
"name": "Sanne de Vries",
"roleId": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e04",
"schedulable": true,
"status": "ACTIVE"
}
],
"hasMore": false,
"nextCursor": "MjAyNi0xMC0wMVQwODozMDowMFp8M2YwYzhhNTItNmIxZS00ZDdhLTljM2UtMWEyYjNjNGQ1ZTAz"
}3. Plan
Create a planning link for the lead. Your backend does this at the moment the caller clicks Plan:
Example request · POST /api/v1/planning-links
IDEMPOTENCY_KEY=$(uuidgen) # once per action; to retry, run only the curl line again (same key)
curl -X POST https://api.schedulinq.com/api/v1/planning-links \
-H "Authorization: Bearer $SCHEDULINQ_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"appointmentTypeId": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e01",
"customer": {
"id": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e06"
},
"metadata": {
"campaignId": "C-42",
"dealId": "D-123"
},
"returnUrl": "https://crm.example.com/deals/D-123"
}'Response · 201
{
"customerId": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e06",
"expiresAt": "2026-10-14T09:15:00+02:00",
"id": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e07",
"url": "https://app.schedulinq.com/plan-link#EXAMPLE-TOKEN",
"userId": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e03"
}Then send the caller's browser to url straight away, for example with an HTTP 303
from your backend. The link opens Schedulinq's planner once, within 15 minutes.
The example's returnUrl is refused until an admin adds its origin under
Integrations → API → Allowed return addresses. Add your CRM's origin there, or
leave returnUrl out for this test. Put the e-mail address of one of your own team
members in user. See Planning links.
4. Invite
Send the customer an invitation to book with the team member. An invitation names its
team members as a list, users: put one of your own in it here, as in the planning link.
Example request · POST /api/v1/invitations
IDEMPOTENCY_KEY=$(uuidgen) # once per action; to retry, run only the curl line again (same key)
curl -X POST https://api.schedulinq.com/api/v1/invitations \
-H "Authorization: Bearer $SCHEDULINQ_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"address": {
"city": "Amsterdam",
"country": "NL",
"line1": "Keizersgracht 100",
"postalCode": "1015 AA"
},
"appointmentTypeId": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e01",
"customer": {
"id": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e06"
},
"metadata": {
"campaignId": "C-42",
"dealId": "D-123"
},
"users": [
{
"id": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e03"
}
]
}'Response · 201
{
"address": {
"city": "Amsterdam",
"country": "NL",
"line1": "Keizersgracht 100",
"postalCode": "1015 AA"
},
"bookingPageClosed": false,
"bookingUrl": "https://schedulinq.com/i/EXAMPLE-INVITATION",
"colleagues": [
{
"id": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e01",
"name": "Offertebezoek"
}
],
"createdAt": "2026-10-01T09:00:00+02:00",
"customerId": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e06",
"id": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e08",
"initialEmailDeferred": false,
"lineageRootId": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e09",
"location": {
"id": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e01",
"name": "Offertebezoek"
},
"locationMode": "VIDEO",
"metadata": {
"campaignId": "C-42",
"dealId": "D-123"
},
"rescheduledFromAppointmentId": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e09",
"service": {
"id": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e01",
"name": "Offertebezoek"
},
"status": "PENDING",
"updatedAt": "2026-10-01T09:00:00+02:00",
"validUntil": "2026-10-31"
}The invitation goes out through your communication steps. Keep bookingUrl if you
need it: it is in this answer only. See Invitations.
5. Create a buyer
Invite a new team member with one of the roles your key may assign (GET /api/v1/roles).
The Idempotency-Key makes the call safe to retry; see
Idempotency.
Example request · POST /api/v1/users
IDEMPOTENCY_KEY=$(uuidgen) # once per action; to retry, run only the curl line again (same key)
curl -X POST https://api.schedulinq.com/api/v1/users \
-H "Authorization: Bearer $SCHEDULINQ_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"customFields": {
"bedrijf": "Loodgieter Bakker"
},
"email": "piet+ov@loodgieter-bakker.example",
"firstName": "Piet",
"lastName": "Bakker",
"locale": "nl",
"metadata": {
"campaignId": "C-42"
},
"roleId": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e04"
}'A new invitation answers 201, and the invitation mail is on its way:
Response · 201
{
"admin": false,
"createdAt": "2026-10-01T09:12:00Z",
"customFields": {
"bedrijf": "Loodgieter Bakker"
},
"email": "piet+ov@loodgieter-bakker.example",
"firstName": "Piet",
"invitationExpiresAt": "2026-10-08T09:12:00Z",
"invitationId": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e05",
"lastName": "Bakker",
"metadata": {
"campaignId": "C-42"
},
"name": "Piet Bakker",
"roleId": "3f0c8a52-6b1e-4d7a-9c3e-1a2b3c4d5e04",
"status": "INVITED"
}Run the same call again with a new Idempotency-Key and the answer is a 200 with
INVITED: the address is already invited, and nothing is sent twice. See
Colleagues.
6. Your first webhook
- In the dashboard, add an endpoint under Integrations → API → Webhooks, and tick the events you want. See Webhooks.
- Click Send test message. Your endpoint receives a
ping. - Verify its signature with the endpoint's signing secret; see Verify the signature.
Your endpoint must be reachable over https at a public address: plain http, localhost and private addresses are refused. For local development, use a tunnelling service that gives your machine a public https address.
Next steps
- Errors: what each code means and what to do about it.
- Idempotency: retrying safely.
- Rate limits: how much you may ask for.
- Reference: every endpoint.