# TimelyDo API Reference Canonical URL: https://timelydo.com/api/docs/ # Introduction ## About TimelyDo [TimelyDo](https://timelydo.com) is a scheduling platform. You publish a booking page, share the link, and people pick a time from your real availability. No back and forth emails, and no double bookings. TimelyDo syncs with Google Calendar, so events already on your calendar block those time slots. It connects to Zoom and Google Meet for the meetings people book with you. Accounts are personal, and organizations can group members together. Using TimelyDo is free. Learn more on the [features pages](https://timelydo.com/features/scheduling) or in the [Help Center](https://timelydo.com/help-center/). ## About the API The TimelyDo API lets you work with your TimelyDo account from your own applications, scripts and integrations. It uses the same data you see in the app, so anything you read or change through the API shows up in TimelyDo straight away. Need help? Email [support@timelydo.com](mailto:support@timelydo.com). Every example on this page comes in Shell (curl), Ruby, Python and JavaScript. Switch languages with the tabs at the top right. ## Base URL All endpoints live under: `https://timelydo.com/api/v1` ## Response format > Every response, success or failure, has the same shape: ```json { "success": true, "message": "user details", "data": {}, "status": 200 } ``` Requests and responses are JSON. Every response carries the same four fields: Field | Type | Description ----- | ---- | ----------- success | boolean | `true` when the request worked, `false` otherwise. message | string | A short human readable description of the result. data | object or array | The payload. An empty object when there is nothing to return. status | integer or string | Mirrors the HTTP status. Successful responses use the number (`200`). Errors use the status name (`"unauthorized"`, `"forbidden"`). Always check the HTTP status code or `success` before reading `data`. # Authentication The TimelyDo API uses API keys. Every request must send your key in the `X-API-KEY` header. Requests without a valid key are rejected with `401 Unauthorized`. ## Get your API key Every TimelyDo account has one API key, created when the account is created. To find it: 1. Sign in at [timelydo.com](https://timelydo.com). 2. Open **Settings**, then **Developers Console**, then **API Key**. You can also go straight to [timelydo.com/settings/developers/api_key](https://timelydo.com/settings/developers/api_key). 3. Click the copy button next to **Your API Key**. Keys are 73 characters long. They do not expire. A key keeps working until you regenerate it or delete your account. The examples on this page read the key from an environment variable called `TIMELYDO_API_KEY`, so it never appears in your code: `export TIMELYDO_API_KEY="paste your key here"` ## Authenticate your requests ```shell curl "https://timelydo.com/api/v1/user" \ -H "X-API-KEY: $TIMELYDO_API_KEY" \ -H "Accept: application/json" ``` ```ruby require 'net/http' require 'json' uri = URI('https://timelydo.com/api/v1/user') request = Net::HTTP::Get.new(uri) request['X-API-KEY'] = ENV.fetch('TIMELYDO_API_KEY') request['Accept'] = 'application/json' response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http| http.request(request) end body = JSON.parse(response.body) if body['success'] puts "Authenticated as #{body['data']['email']}" else warn "#{response.code}: #{body['message']}" end ``` ```python import os import requests response = requests.get( "https://timelydo.com/api/v1/user", headers={ "X-API-KEY": os.environ["TIMELYDO_API_KEY"], "Accept": "application/json", }, timeout=10, ) body = response.json() if body["success"]: print("Authenticated as", body["data"]["email"]) else: print(f"{response.status_code}: {body['message']}") ``` ```javascript // Node.js 18 or newer (built in fetch). Run this on your server, never in a browser. async function getCurrentUser() { const response = await fetch("https://timelydo.com/api/v1/user", { headers: { "X-API-KEY": process.env.TIMELYDO_API_KEY, "Accept": "application/json", }, }); const body = await response.json(); if (body.success) { console.log(`Authenticated as ${body.data.email}`); } else { console.error(`${response.status}: ${body.message}`); } } getCurrentUser(); ``` > A valid key returns the account it belongs to (trimmed): ```json { "success": true, "message": "user details", "data": { "id": "3f6c2a9e-8b1d-4c7a-9e21-5d0b7f4a1c33", "email": "jane@example.com", "full_name": "Jane Doe", "url": "jane", "avatar_name": "JD", "timezone": "Berlin", "date_timezone": "Europe/Berlin", "locale": "en", "time_format": "12_hours", "date_format": "31/12/2024", "currency": "eur", "country_iso_code": "de", "api_key": "YOUR_API_KEY", "created_at": "2026-08-09T04:23:16.260Z" }, "status": 200 } ``` > A missing or wrong key returns `401`: ```json { "success": false, "message": "Invalid api_key", "data": {}, "status": "unauthorized" } ``` Send the key in the `X-API-KEY` header on every request: `X-API-KEY: YOUR_API_KEY` The header name is not case sensitive. The key itself must match exactly. The quickest way to check a key is `GET /api/v1/user`. It returns the account the key belongs to. ### HTTP Request `GET https://timelydo.com/api/v1/user` ### Authentication errors HTTP status | message | Cause ----------- | ------- | ----- 401 | `api_key is missing` | No `X-API-KEY` header was sent. 401 | `Invalid api_key` | The key does not match any active account. It may have been regenerated, or the account was deleted. 403 | depends on the endpoint | The endpoint only works from a signed in browser session. See below. ### Endpoints that need a signed in session A few account security settings cannot be changed with an API key, even a valid one. This protects your account if a key ever leaks. These endpoints return `403 Forbidden` for API key requests: Endpoint | 403 message -------- | ----------- `/api/v1/two_factor` (all methods) and `/api/v1/two_factor/backup_codes` | `Sign in to the app to manage two factor authentication.` `/api/v1/hellobar` and its actions | `A signed in session is required` Manage these from the TimelyDo app instead. ## Regenerate your API key Regenerate your key if it may have leaked, or to rotate it on a schedule. The old key stops working **immediately**, so update every integration that uses it right away. You can regenerate the key in two ways. **In the app** 1. Open **Settings**, then **Developers Console**, then **API Key**. 2. Click **Regenerate key**. 3. Copy the new key shown on the page. **With the API** ```shell curl -X PUT "https://timelydo.com/api/v1/users/regenerate_api_key" \ -H "X-API-KEY: $TIMELYDO_API_KEY" \ -H "Accept: application/json" ``` ```ruby require 'net/http' require 'json' uri = URI('https://timelydo.com/api/v1/users/regenerate_api_key') request = Net::HTTP::Put.new(uri) request['X-API-KEY'] = ENV.fetch('TIMELYDO_API_KEY') request['Accept'] = 'application/json' response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http| http.request(request) end body = JSON.parse(response.body) if body['success'] new_api_key = body['data']['api_key'] # Save new_api_key somewhere safe now. The old key has already stopped working. puts "New key received (#{new_api_key.length} characters)" else warn "#{response.code}: #{body['message']}" end ``` ```python import os import requests response = requests.put( "https://timelydo.com/api/v1/users/regenerate_api_key", headers={ "X-API-KEY": os.environ["TIMELYDO_API_KEY"], "Accept": "application/json", }, timeout=10, ) body = response.json() if body["success"]: new_api_key = body["data"]["api_key"] # Save new_api_key somewhere safe now. The old key has already stopped working. print(f"New key received ({len(new_api_key)} characters)") else: print(f"{response.status_code}: {body['message']}") ``` ```javascript // Node.js 18 or newer (built in fetch). Run this on your server, never in a browser. async function regenerateApiKey() { const response = await fetch("https://timelydo.com/api/v1/users/regenerate_api_key", { method: "PUT", headers: { "X-API-KEY": process.env.TIMELYDO_API_KEY, "Accept": "application/json", }, }); const body = await response.json(); if (body.success) { const newApiKey = body.data.api_key; // Save newApiKey somewhere safe now. The old key has already stopped working. console.log(`New key received (${newApiKey.length} characters)`); } else { console.error(`${response.status}: ${body.message}`); } } regenerateApiKey(); ``` > The response contains the new key: ```json { "success": true, "message": "api_key is updated", "data": { "api_key": "YOUR_NEW_API_KEY" }, "status": 200 } ``` Authenticate this request with your **current** key. The response returns the new key, and from that moment only the new key works. ### HTTP Request `PUT https://timelydo.com/api/v1/users/regenerate_api_key` This endpoint takes no parameters. ### If you lost your key You cannot get a key back through the API without a working key. Sign in to TimelyDo and copy it from the API Key page, or regenerate a new one there. # Errors > Errors use the same response shape as successful requests: ```json { "success": false, "message": "missing params", "data": { "user": ["profile_picture"] }, "status": "bad_request" } ``` When a request fails, `success` is `false`, `message` says what went wrong, and `status` holds the HTTP status name. For some errors `data` carries extra detail, such as the list of missing fields. HTTP status | status field | Meaning ----------- | ------------ | ------- 400 | `bad_request` | Required parameters are missing. `data` lists them, grouped by object. 401 | `unauthorized` | The `X-API-KEY` header is missing, or the key is not valid. See [Authentication](#authentication). 403 | `forbidden` | Your key is valid, but this endpoint only works from a signed in browser session. 404 | `not_found` | The endpoint does not exist (`endpoint does not exists`), or the record you asked for was not found. 422 | `unprocessable_entity` | The request was understood, but the values failed validation. `message` explains which ones. 500 | `internal_server_error` | Something went wrong on our side. Try again later. If it keeps happening, contact support.