timeless-api/SKILL.md
Query and manage Timeless meetings, rooms, transcripts, and AI documents. Capture podcast episodes and YouTube videos into Timeless for transcription. Use when the user asks about their meetings, wants to search meetings, read transcripts, get summaries, list rooms, create rooms, add/remove conversations from rooms, resolve Timeless share links, upload recordings, chat with Timeless AI about meeting content, or capture podcasts/YouTube videos.
npx skillsauth add supertools/timeless-skills timeless-apiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Source: github.com/supertools/timeless-skills
Interact with Timeless meeting data: search meetings, read transcripts, get AI summaries, browse rooms, upload recordings, chat with the AI agent, and capture podcasts/YouTube videos for transcription.
For full endpoint documentation with response schemas, status enums, and detailed examples, read ../api-reference.md.
Official API docs: docs.timeless.day
TIMELESS_ACCESS_TOKEN env var (get token at my.timeless.day/api-token)yt-dlp for YouTube downloads (install via package manager: apt install yt-dlp, brew install yt-dlp, or pip install yt-dlp. Alternatively set YTDLP_PATH to point to an existing binary.)Set up in OpenClaw:
openclaw config patch env.vars.TIMELESS_ACCESS_TOKEN=<your_token>
This skill uses two APIs. Prefer the official API when available.
Official API (meetings, rooms, transcripts, recordings, documents, uploads, webhooks):
https://api.timeless.day/v1
Authorization: Bearer $TIMELESS_ACCESS_TOKEN
Unofficial API (spaces, AI chat, room management, scheduling, share URLs):
https://my.timeless.day
Authorization: Token $TIMELESS_ACCESS_TOKEN
The same
TIMELESS_ACCESS_TOKENworks for both. The header format differs:Bearerfor official,Tokenfor unofficial.
GET https://api.timeless.day/v1/meetings
| Parameter | Type | Description |
|-----------|------|-------------|
| scope | string | all (default), owned, or shared |
| search | string | Search by meeting title (substring match) |
| q | string | Semantic search across meeting content |
| start_date | string | From date (YYYY-MM-DD) |
| end_date | string | To date (YYYY-MM-DD) |
| status | string | completed, processing, scheduled, failed |
| participant | string | Filter by participant name or email |
| company | string | Filter by company name or domain |
| room_id | string | Filter by room ID (e.g. room_abc123) |
| expand | string | documents to include AI documents inline |
| limit | integer | Results per page, 1-100 (default: 25) |
| cursor | string | Pagination cursor from previous response |
curl -s "https://api.timeless.day/v1/meetings?scope=owned&status=completed&limit=50" \
-H "Authorization: Bearer $TIMELESS_ACCESS_TOKEN"
Response:
{
"data": [
{
"id": "mtg_abc123",
"title": "Weekly standup",
"status": "completed",
"source": "google_meet",
"start_time": "2025-01-15T10:00:00Z",
"end_time": "2025-01-15T10:30:00Z",
"duration": 1800,
"host": { "id": "usr_def456", "name": "Alice Johnson", "email": "[email protected]" },
"participants": [
{ "name": "Bob Smith", "email": "[email protected]", "title": "Engineer", "company": "Acme Corp" }
],
"created_at": "2025-01-15T10:00:00Z"
}
],
"next_cursor": "eyJjcmVhdGVkX2F0Ijo...",
"has_more": true
}
Key fields:
id = meeting ID (use for Get Transcript, Get Recording, Get Document)scope=all to get both owned and shared meetings in one callPagination: pass next_cursor as cursor param to get the next page. Repeat while has_more is true.
GET https://api.timeless.day/v1/rooms
| Parameter | Type | Description |
|-----------|------|-------------|
| scope | string | all (default), owned, or shared |
| search | string | Search by room title |
| expand | string | documents, meetings, or both (expand=documents&expand=meetings) |
| limit | integer | Results per page, 1-100 (default: 25) |
| cursor | string | Pagination cursor from previous response |
curl -s "https://api.timeless.day/v1/rooms?scope=owned&expand=meetings" \
-H "Authorization: Bearer $TIMELESS_ACCESS_TOKEN"
Response:
{
"data": [
{
"id": "room_abc123",
"title": "Engineering standups",
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-15T10:30:00Z",
"meeting_count": 42,
"meetings": [
{ "id": "mtg_abc123", "title": "Weekly standup", "status": "completed", "source": "google_meet", "start_time": "2025-01-15T10:00:00Z", "duration": 1800 }
]
}
],
"next_cursor": null,
"has_more": false
}
This endpoint uses the unofficial API (
https://my.timeless.day) and returns rich data not available via the official API: conversations, artifacts, contacts, organizations, and AI chat threads.
Spaces have three access levels. Try in order until one succeeds:
curl -s "https://my.timeless.day/api/v1/spaces/{uuid}/" \
-H "Authorization: Token $TIMELESS_ACCESS_TOKEN"
host_uuidis required for shared spaces. Get it from thehost_user.uuidfield in the List Meetings or List Rooms response.
curl -s "https://my.timeless.day/api/v1/spaces/{uuid}/workspace/?host_uuid={hostUuid}" \
-H "Authorization: Token $TIMELESS_ACCESS_TOKEN"
curl -s "https://my.timeless.day/api/v1/spaces/public/{uuid}/{hostUuid}/" \
-H "Authorization: Token $TIMELESS_ACCESS_TOKEN"
Response includes:
conversations[]: Recordings in this space (each has uuid, name, start_ts, end_ts, status, language)artifacts[]: AI-generated documents. Check type field (e.g., "summary"). Content is in content.body (HTML).contacts[]: Each has nested conversations[]organizations[]: Each has nested conversations[]threads[]: AI chat threads. Use threads[0].uuid to chat with the agent.Collecting all conversations in a room:
Deduplicate conversation UUIDs from conversations[] + contacts[].conversations[] + organizations[].conversations[].
GET https://api.timeless.day/v1/meetings/{meeting_id}/transcript
curl -s "https://api.timeless.day/v1/meetings/mtg_abc123/transcript" \
-H "Authorization: Bearer $TIMELESS_ACCESS_TOKEN"
Response:
{
"meeting_id": "mtg_abc123",
"language": "en",
"speakers": [
{ "id": "spk_001", "name": "Alice Johnson" },
{ "id": "spk_002", "name": "Bob Smith" }
],
"segments": [
{ "speaker_id": "spk_001", "start_time": 0, "end_time": 4.5, "text": "Good morning everyone, let's get started." },
{ "speaker_id": "spk_002", "start_time": 4.8, "end_time": 8.2, "text": "Sounds good. I have updates on the project." }
]
}
How to get meeting_id: from the id field in List Meetings response.
Format as readable text by mapping speaker_id to speaker names:
[00:00:00] Alice Johnson: Good morning everyone, let's get started.
[00:00:04] Bob Smith: Sounds good. I have updates on the project.
For room conversations: To fetch transcripts for individual conversations within a room (accessed via Get Space), use the unofficial transcript endpoint:
GET https://my.timeless.day/api/v1/conversation/{conversation_uuid}/transcript/withAuthorization: Token.
GET https://api.timeless.day/v1/meetings/{meeting_id}/recording
curl -s "https://api.timeless.day/v1/meetings/mtg_abc123/recording" \
-H "Authorization: Bearer $TIMELESS_ACCESS_TOKEN"
Response:
{
"meeting_id": "mtg_abc123",
"recording_url": "https://storage.example.com/recordings/abc123?token=..."
}
The URL is time-limited. Fetch it fresh when needed.
recording_urlisnullif no recording is available.
PUT https://api.timeless.day/v1/meetings/upload
Single-step upload: send the raw file as the request body with metadata as query parameters.
| Parameter | Location | Description |
|-----------|----------|-------------|
| title | query | Optional title for the recording (max 500 chars) |
| language | query | Language code for transcription (e.g. en, es) |
| Content-Type | header | MIME type of the file |
Supported types: audio/mpeg, audio/mp4, audio/wav, audio/webm, audio/ogg, audio/aac, audio/flac, video/mp4, video/webm, video/ogg, video/quicktime
curl -X PUT "https://api.timeless.day/v1/meetings/upload?title=Team%20sync&language=en" \
-H "Authorization: Bearer $TIMELESS_ACCESS_TOKEN" \
-H "Content-Type: audio/mpeg" \
--data-binary @recording.mp3
Response:
{
"id": "mtg_abc123",
"status": "processing"
}
Poll GET /meetings?id=mtg_abc123 until status changes from processing to completed.
Or use the helper script: bash ../scripts/upload.sh FILE_PATH LANGUAGE [TITLE]
Supported file extensions: mp3, wav, m4a, mp4, webm, ogg, aac, flac, mov
URLs like https://my.timeless.day/m/ENCODED_ID contain two Base64-encoded short IDs (22 chars each).
Decoding (shell):
ENCODED="the_part_after_/m/"
DECODED=$(echo "$ENCODED" | base64 -d)
SPACE_ID=$(echo "$DECODED" | cut -c1-22)
HOST_ID=$(echo "$DECODED" | cut -c23-44)
Decoding (Python):
import base64
def decode_timeless_url(url):
encoded = url.rstrip('/').split('/m/')[-1]
combined = base64.b64decode(encoded).decode()
return combined[:22], combined[22:] # (space_id, host_id)
After decoding, fetch with Get Space (try private -> workspace -> public).
Ask questions about a meeting or room.
curl -X POST "https://my.timeless.day/api/v1/agent/space/chat/" \
-H "Authorization: Token $TIMELESS_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"space_uuid": "SPACE_UUID",
"thread_uuid": "THREAD_UUID",
"message": {
"role": "user",
"parts": [{"type": "text", "text": "What were the action items?"}],
"date": "'$(date -u +%Y-%m-%dT%H:%M:%S.000Z)'",
"metadata": {"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%S.000Z)'", "mentions": []},
"id": "'$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen)'"
}
}'
Get thread_uuid from the space's threads[0].uuid (via Get Space).
curl -s "https://my.timeless.day/api/v1/agent/threads/{thread_uuid}/" \
-H "Authorization: Token $TIMELESS_ACCESS_TOKEN"
Poll every 2-3 seconds until is_running is false. The AI response is the last message with role: "assistant" in the messages array.
curl -X POST "https://my.timeless.day/api/v1/spaces/" \
-H "Authorization: Token $TIMELESS_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"has_onboarded": true, "space_type": "ROOM", "title": "My Room"}'
Response: Full space object. Extract uuid for adding resources.
# Add a conversation
curl -X POST "https://my.timeless.day/api/v1/spaces/{room_uuid}/resources/" \
-H "Authorization: Token $TIMELESS_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"resource_type": "CONVERSATION", "resource_uuid": "CONVERSATION_UUID"}'
# Remove a conversation
curl -X DELETE "https://my.timeless.day/api/v1/spaces/{room_uuid}/resources/" \
-H "Authorization: Token $TIMELESS_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"resource_type": "CONVERSATION", "resource_uuid": "CONVERSATION_UUID"}'
Call Add once per conversation you want to attach. Get conversation UUIDs from Get Space (conversations[].uuid).
GET https://api.timeless.day/v1/documents/{document_id}
Fetch an AI-generated document (summary, action items, notes) in your preferred format.
| Parameter | Type | Description |
|-----------|------|-------------|
| format | string | html (default), markdown, raw, docx, json |
curl -s "https://api.timeless.day/v1/documents/doc_abc123?format=markdown" \
-H "Authorization: Bearer $TIMELESS_ACCESS_TOKEN"
Response:
{
"id": "doc_abc123",
"title": "Meeting summary",
"format": "markdown",
"content": "# Weekly standup\n\n## Key decisions\n- Proceed with the new API design\n\n## Action items\n- Alice: Update the spec by Friday\n",
"created_at": "2025-01-15T10:35:00Z"
}
How to get document IDs: Use expand=documents when listing meetings or rooms. Each meeting/room will include a documents[] array with id, title, and created_at.
Subscribe to events so Timeless notifies you when transcripts or summaries are ready.
Available events: meeting.transcript_ready, meeting.initial_summary_ready
curl -X POST "https://api.timeless.day/v1/webhooks" \
-H "Authorization: Bearer $TIMELESS_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/timeless",
"events": ["meeting.transcript_ready", "meeting.initial_summary_ready"]
}'
Response: Returns the webhook object including a secret for signature verification. Store the secret securely; it is only returned at creation time.
curl -s "https://api.timeless.day/v1/webhooks" \
-H "Authorization: Bearer $TIMELESS_ACCESS_TOKEN"
curl -X PATCH "https://api.timeless.day/v1/webhooks/whk_abc123" \
-H "Authorization: Bearer $TIMELESS_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
curl -X DELETE "https://api.timeless.day/v1/webhooks/whk_abc123" \
-H "Authorization: Bearer $TIMELESS_ACCESS_TOKEN"
Every delivery includes an X-Webhook-Signature header (sha256=<hex digest>). Verify using HMAC-SHA256 with your webhook secret:
import hashlib, hmac
def verify_signature(payload: bytes, signature_header: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
received = signature_header.removeprefix("sha256=")
return hmac.compare_digest(expected, received)
Delivery behavior: 10s timeout, retries up to 3 times (1s, 10s, 60s) on 5xx/429/network failures.
GET /meetings?scope=owned&status=completed&limit=100next_cursoridGET /meetings?scope=owned&status=completed&expand=documentsdocuments[], find the document you wantGET /documents/{document_id}?format=markdownconversations[], contacts[].conversations[], organizations[].conversations[] (deduplicate)search=your+query or use semantic search with q=your+questionidGET /meetings/{id}/transcriptexpand=documents in the list call, then GET /documents/{doc_id}?format=markdownUse webhooks to react to new meetings automatically without polling.
POST /webhooks with your URL and desired eventsFor environments that cannot receive webhooks, use cron polling with a state file.
A cron job runs every 5-10 minutes. Each run:
timeless-processed.json). Create it with an empty processed array if missing.GET https://api.timeless.day/v1/meetings?scope=owned&status=completed&start_date=YYYY-MM-DDid is already in processed, skip it.State file format:
{
"processed": ["mtg_abc123", "mtg_def456"],
"last_check": "2026-03-05T12:00:00Z"
}
Key rules:
processed is never processed again. This prevents duplicate work.Cron setup (OpenClaw):
openclaw cron add "timeless-poll" --schedule "*/5 * * * *" --task "Check for new completed Timeless meetings. Read timeless-processed.json for state. Poll the API. For new meetings: [your automation here]. If nothing new, reply HEARTBEAT_OK."
Once a new completed meeting is detected (via webhook or polling), you have access to:
artifacts[])Combine these with any external tool or API. Some examples of what people build:
The pattern is always the same: detect new meetings, pull the data, do your thing.
Scripts in ../scripts/ folder.
bash ../scripts/podcast.sh search "podcast name"bash ../scripts/podcast.sh episodes FEED_URL [limit]bash ../scripts/podcast.sh download MP3_URL /tmp/episode.mp3bash ../scripts/upload.sh /tmp/episode.mp3 en "Episode Title"Extract the episode title via oEmbed, then search by name:
curl -s "https://open.spotify.com/oembed?url=SPOTIFY_URL"
bash ../scripts/youtube.sh info "YOUTUBE_URL"bash ../scripts/youtube.sh download "YOUTUBE_URL" /tmp/video.mp4bash ../scripts/upload.sh /tmp/video.mp4 en "Video Title"Downloads as mp4 (video+audio). No ffmpeg needed. Uses the best pre-muxed format (typically 720p), which is fine for Timeless.
After uploading, attach the content to a Timeless room for organized collections.
id. Poll GET https://api.timeless.day/v1/meetings?id={meeting_id} until status is completed.GET https://my.timeless.day/api/v1/spaces/{uuid}/ and extract conversations[0].uuid.POST https://my.timeless.day/api/v1/spaces/{room_uuid}/resources/ with {"resource_type": "CONVERSATION", "resource_uuid": "CONV_UUID"}To create a new room first: POST https://my.timeless.day/api/v1/spaces/ with {"has_onboarded": true, "space_type": "ROOM", "title": "My Collection"}
YTDLP_PATH env var if yt-dlp is not on PATH.mtg_, room_, doc_, whk_). Unofficial API uses plain UUIDs. Do not mix them across APIs.Official API returns structured errors:
{ "error": { "code": "not_found", "message": "Resource not found" } }
| Code | Action |
|------|--------|
| 401 | Token expired. Re-authenticate at my.timeless.day/api-token |
| 403 | No access. For unofficial API, try workspace or public endpoint. |
| 404 | Not found. Check ID/UUID. |
| 429 | Rate limited. Check Retry-After header and wait. |
Official API includes rate limit headers on every response:
| Endpoint | Limit | |----------|-------| | Most endpoints | 60 requests/minute | | Webhook creation | 20 requests/minute | | File upload | 10 requests/minute |
Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
Unofficial API: No official limits. Be respectful: 0.5s delay between sequential requests, max ~60 requests per minute.
data-ai
Schedule meetings using Timeless scheduling system with productivity-first slot selection. Create scheduling invites with curated time slots and share scheduling links. Use when the user wants to schedule a meeting, find available times, send a scheduling link, create a meeting invite, or coordinate a call with someone.
development
Maintainer-only workflow for handling GitHub Secret Scanning alerts on OpenClaw. Use when Codex needs to triage, redact, clean up, and resolve secret leakage found in issue comments, issue bodies, PR comments, or other GitHub content.
development
Maintainer workflow for OpenClaw releases, prereleases, changelog release notes, and publish validation. Use when Codex needs to prepare or verify stable or beta release steps, align version naming, assemble release notes, check release auth requirements, or validate publish-time commands and artifacts.
development
Run, watch, debug, and extend OpenClaw QA testing with qa-lab and qa-channel. Use when Codex needs to execute the repo-backed QA suite, inspect live QA artifacts, debug failing scenarios, add new QA scenarios, or explain the OpenClaw QA workflow. Prefer the live OpenAI lane with regular openai/gpt-5.4 in fast mode; do not use gpt-5.4-pro or gpt-5.4-mini unless the user explicitly overrides that policy.