API Documentation

Build AI agents and integrations with the Intervals API.

Base URL: https://intervals.so/api/v1

Authentication

All API requests require an API key in the Authorization header:

Authorization: Bearer ivl_live_xxxxxxxxxxxxx

Generate API keys in your dashboard → Settings → API Keys.

Rate Limits

1,000 requests/day — contact us if you need more.

MCP Server

Prefer agents over raw HTTP? Connect Claude or ChatGPT with browser sign-in (OAuth), or Cursor with an API key.

https://intervals.so/api/mcp

Easy-login setup, Cursor config, and tool list: /docs/mcp

Error Handling

All errors return a consistent JSON format:

{
  "error": "Human-readable error message",
  "message": "Additional details (optional)",
  "details": [...]  // Validation errors only
}

Common Error Codes

400Bad Request — Invalid parameters or missing required fields
401Unauthorized — Invalid or missing API key
403Forbidden — API access not available for your tier
404Not Found — Resource doesn't exist
429Too Many Requests — Rate limit exceeded
500Internal Server Error — Something went wrong

Profile

GET/profile

Get your profile information

Request

curl https://intervals.so/api/v1/profile \
  -H "Authorization: Bearer ivl_live_..."

Response

{
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Ruzgar",
  "timezone": "Europe/Tallinn",
  "subscription_tier": "pro",
  "calendar_connected": true,
  "default_duration": 30,
  "api_key_name": "My AI Assistant"
}

Response Fields

user_id(string)— Your unique user ID
name(string)— Display name
timezone(string)— IANA timezone (e.g., "Europe/Tallinn")
subscription_tier(string)— "free", "beta", or "pro"
calendar_connected(boolean)— Whether Google Calendar is connected
default_duration(number)— Default meeting duration in minutes
api_key_name(string)— Name of the API key used for this request

Availability

GET/availability

Check availability for a specific date

Query Parameters

date(string)required— Date in YYYY-MM-DD format

Request

curl "https://intervals.so/api/v1/availability?date=2026-02-06" \
  -H "Authorization: Bearer ivl_live_..."

Response

{
  "date": "2026-02-06",
  "timezone": "Europe/Tallinn",
  "day_of_week": "friday",
  "working_hours": {
    "start": "09:00",
    "end": "17:00"
  },
  "intervals": [
    { "title": "Friday afternoon", "time_range": "13:00 – 17:00" }
  ],
  "busy_slots": [
    { "start": "14:00", "end": "14:30", "title": "Team Standup" }
  ],
  "available_slots": [
    { "start": "13:00", "end": "14:00" },
    { "start": "14:30", "end": "17:00" }
  ]
}

Response Fields

date(string)— The requested date
timezone(string)— User timezone
day_of_week(string)— Day name (lowercase)
working_hours(object)— Overall working window for the day
intervals(array)— Active availability intervals for this day
busy_slots(array)— Calendar events blocking time
available_slots(array)— Free time windows
GET/availability/slots

Get bookable time slots for a date range. Ideal for AI agents finding meeting times.

Query Parameters

start(string)required— Start date (YYYY-MM-DD)
end(string)required— End date (YYYY-MM-DD), max 14 days from start
duration(number)— Meeting duration in minutes (default: user setting)
buffer_before(number)— Buffer before meeting in minutes (default: 15)
buffer_after(number)— Buffer after meeting in minutes (default: 15)
limit(number)— Max slots to return (default: 10, max: 50)

Request

curl "https://intervals.so/api/v1/availability/slots?start=2026-02-06&end=2026-02-10&duration=30&limit=5" \
  -H "Authorization: Bearer ivl_live_..."

Response

{
  "timezone": "Europe/Tallinn",
  "duration_minutes": 30,
  "buffer_before_minutes": 15,
  "buffer_after_minutes": 15,
  "date_range": { "start": "2026-02-06", "end": "2026-02-10" },
  "total_slots": 5,
  "slots": [
    {
      "date": "2026-02-06",
      "start": "13:00",
      "end": "13:30",
      "iso_start": "2026-02-06T11:00:00.000Z",
      "iso_end": "2026-02-06T11:30:00.000Z"
    },
    {
      "date": "2026-02-06",
      "start": "15:00",
      "end": "15:30",
      "iso_start": "2026-02-06T13:00:00.000Z",
      "iso_end": "2026-02-06T13:30:00.000Z"
    }
  ]
}

Response Fields

slots[].date(string)— Date of the slot (YYYY-MM-DD)
slots[].start(string)— Start time in local timezone (HH:MM)
slots[].end(string)— End time in local timezone (HH:MM)
slots[].iso_start(string)— Start time in ISO 8601 UTC
slots[].iso_end(string)— End time in ISO 8601 UTC

Calendar Events

GET/events

List calendar events for a date range

Query Parameters

start(string)required— Start date (YYYY-MM-DD)
end(string)required— End date (YYYY-MM-DD), max 30 days from start
include_all_day(boolean)— Include all-day events (default: true)

Request

curl "https://intervals.so/api/v1/events?start=2026-02-06&end=2026-02-06" \
  -H "Authorization: Bearer ivl_live_..."

Response

{
  "timezone": "Europe/Tallinn",
  "date_range": { "start": "2026-02-06", "end": "2026-02-06" },
  "total_events": 2,
  "events": [
    {
      "id": "abc123xyz",
      "title": "Team Standup",
      "start": "2026-02-06T09:00:00+02:00",
      "end": "2026-02-06T09:30:00+02:00",
      "all_day": false,
      "description": "Daily sync",
      "attendees": ["alice@example.com", "bob@example.com"],
      "meet_link": "https://meet.google.com/abc-def-ghi",
      "status": "confirmed",
      "is_recurring": true,
      "is_intervals_event": false
    }
  ]
}

Response Fields

events[].id(string)— Unique event ID (use for PATCH/DELETE)
events[].title(string)— Event title
events[].start(string)— Start time (ISO 8601 with timezone)
events[].end(string)— End time (ISO 8601 with timezone)
events[].all_day(boolean)— Whether this is an all-day event
events[].attendees(string[])— List of attendee emails
events[].meet_link(string | null)— Google Meet link if present
events[].status(string)— "confirmed", "tentative", or "cancelled"
events[].is_intervals_event(boolean)— Whether created via Intervals
POST/events

Create a new calendar event

Request Body

title(string)required— Event title
start(string)required— Start time (ISO 8601)
end(string)required— End time (ISO 8601)
description(string)— Event description
attendee_email(string)— Guest email (adds Google Meet link)

Request

curl -X POST "https://intervals.so/api/v1/events" \
  -H "Authorization: Bearer ivl_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Coffee Chat with Sarah",
    "start": "2026-02-07T14:00:00Z",
    "end": "2026-02-07T14:30:00Z",
    "attendee_email": "sarah@example.com"
  }'

Response

{
  "success": true,
  "event": {
    "id": "event123abc",
    "title": "Coffee Chat with Sarah",
    "start": "2026-02-07T14:00:00.000Z",
    "end": "2026-02-07T14:30:00.000Z",
    "meet_link": "https://meet.google.com/xyz-abc-123"
  }
}
PATCH/events/:id

Update an existing calendar event. Only provided fields are updated.

Request Body

title(string)— New event title
start(string)— New start time (ISO 8601)
end(string)— New end time (ISO 8601)
description(string)— New description

Request

curl -X PATCH "https://intervals.so/api/v1/events/abc123xyz" \
  -H "Authorization: Bearer ivl_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Updated: Coffee Chat",
    "start": "2026-02-07T15:00:00Z",
    "end": "2026-02-07T15:30:00Z"
  }'

Response

{
  "success": true,
  "event": {
    "id": "abc123xyz",
    "title": "Updated: Coffee Chat",
    "start": "2026-02-07T15:00:00.000Z",
    "end": "2026-02-07T15:30:00.000Z"
  }
}

Errors

400— At least one field must be provided
404— Event not found
DELETE/events/:id

Cancel/delete a calendar event. Attendees are notified of cancellation.

Request

curl -X DELETE "https://intervals.so/api/v1/events/abc123xyz" \
  -H "Authorization: Bearer ivl_live_..."

Response

{
  "success": true,
  "event_id": "abc123xyz",
  "message": "Event cancelled successfully"
}

Errors

404— Event not found

Tasks

GET/tasks

List all tasks with optional filters

Query Parameters

status(string)— "pending", "completed", or "dropped"
priority(string)— "low", "medium", "high", or "urgent"
due_date(string)— Filter by due date (YYYY-MM-DD)
limit(number)— Max results (default: 50, max: 100)
offset(number)— Pagination offset (default: 0)
sort(string)— Sort field, prefix with - for desc (e.g., "-priority", "due_date")

Request

curl "https://intervals.so/api/v1/tasks?status=pending&priority=high" \
  -H "Authorization: Bearer ivl_live_..."

Response

{
  "tasks": [
    {
      "id": "task-uuid-123",
      "title": "Ship API documentation",
      "description": "Complete API docs with examples",
      "status": "pending",
      "priority": "high",
      "due_date": "2026-02-06",
      "created_at": "2026-02-05T10:00:00Z",
      "completed_at": null
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}

Response Fields

tasks[].id(string)— Unique task ID (UUID)
tasks[].title(string)— Task title
tasks[].description(string | null)— Task description
tasks[].status(string)— "pending", "completed", or "dropped"
tasks[].priority(string)— "low", "medium", "high", or "urgent"
tasks[].due_date(string | null)— Due date (YYYY-MM-DD) or null
tasks[].completed_at(string | null)— Completion timestamp or null
GET/tasks/today

Get today's tasks (due today + overdue + no due date)

Query Parameters

include_overdue(boolean)— Include overdue tasks (default: true)

Request

curl "https://intervals.so/api/v1/tasks/today" \
  -H "Authorization: Bearer ivl_live_..."

Response

{
  "date": "2026-02-06",
  "timezone": "Europe/Tallinn",
  "total": 3,
  "tasks": [...],
  "categorized": {
    "due_today": [...],
    "overdue": [...],
    "no_due_date": [...]
  }
}
POST/tasks

Create a new task

Request Body

title(string)required— Task title
description(string)— Task description
priority(string)— "low", "medium" (default), "high", or "urgent"
due_date(string)— Due date (YYYY-MM-DD)

Request

curl -X POST "https://intervals.so/api/v1/tasks" \
  -H "Authorization: Bearer ivl_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Review pull request",
    "priority": "high",
    "due_date": "2026-02-06"
  }'

Response

{
  "task": {
    "id": "task-uuid-456",
    "title": "Review pull request",
    "description": null,
    "status": "pending",
    "priority": "high",
    "due_date": "2026-02-06",
    "created_at": "2026-02-06T10:00:00Z",
    "completed_at": null
  }
}
PATCH/tasks/:id

Update a task

Request Body

title(string)— New title
description(string)— New description
status(string)— "pending", "completed", or "dropped"
priority(string)— "low", "medium", "high", or "urgent"
due_date(string)— Due date (YYYY-MM-DD) or null to clear

Request

curl -X PATCH "https://intervals.so/api/v1/tasks/task-uuid-456" \
  -H "Authorization: Bearer ivl_live_..." \
  -H "Content-Type: application/json" \
  -d '{"status": "completed"}'

Response

{
  "task": {
    "id": "task-uuid-456",
    "title": "Review pull request",
    "status": "completed",
    "completed_at": "2026-02-06T12:00:00Z",
    ...
  }
}
POST/tasks/:id/complete

Mark a task as completed (shortcut for PATCH with status=completed)

Request

curl -X POST "https://intervals.so/api/v1/tasks/task-uuid-456/complete" \
  -H "Authorization: Bearer ivl_live_..."

Response

{
  "task": { ... },
  "message": "Task marked as completed"
}
DELETE/tasks/:id

Delete a task permanently

Request

curl -X DELETE "https://intervals.so/api/v1/tasks/task-uuid-456" \
  -H "Authorization: Bearer ivl_live_..."

Response

{
  "success": true,
  "message": "Task deleted successfully"
}

Intervals (Availability Windows)

GET/intervals

List your availability intervals

Query Parameters

active_only(boolean)— Only return active intervals (default: true)

Request

curl "https://intervals.so/api/v1/intervals" \
  -H "Authorization: Bearer ivl_live_..."

Response

{
  "timezone": "Europe/Tallinn",
  "default_duration": 30,
  "total_intervals": 2,
  "intervals": [
    {
      "id": "interval-uuid-1",
      "title": "Weekday mornings",
      "time_range": "9:00 – 12:00",
      "is_active": true,
      "days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
      "start_time": "09:00",
      "end_time": "12:00"
    },
    {
      "id": "interval-uuid-2",
      "title": "Friday afternoon",
      "time_range": "14:00 – 17:00",
      "is_active": true,
      "days": ["friday"],
      "start_time": "14:00",
      "end_time": "17:00"
    }
  ]
}
POST/intervals

Create a new availability interval

Request Body

title(string)required— Name with day info (e.g., "Monday morning", "Weekday afternoons")
time_range(string)required— Time range in "HH:MM – HH:MM" format

Request

curl -X POST "https://intervals.so/api/v1/intervals" \
  -H "Authorization: Bearer ivl_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Saturday morning",
    "time_range": "10:00 – 13:00"
  }'

Response

{
  "success": true,
  "interval": {
    "id": "interval-uuid-3",
    "title": "Saturday morning",
    "time_range": "10:00 – 13:00",
    "is_active": true
  }
}

Agent Discovery

For AI agent-to-agent meeting scheduling. See our IANP Protocol docs for full details.

GET/agent/discover

Discover a user for meeting negotiation

Query Parameters

key(string)— Public agent key (ivl_pub_xxx) - preferred
username(string)— Username - alternative to key

Request

curl "https://intervals.so/api/v1/agent/discover?key=ivl_pub_abc123" \
  -H "Authorization: Bearer ivl_live_..."

Response

{
  "found": true,
  "user": {
    "public_key": "ivl_pub_abc123",
    "username": "ruzgar",
    "display_name": "Ruzgar"
  },
  "negotiation_endpoint": "https://intervals.so/api/v1/agent-negotiation",
  "protocol": {
    "name": "IANP",
    "version": "1.0"
  }
}

Need help with the API?

hello@intervals.so