---
name: demodesk-api
description: Query Demodesk recordings, transcripts, summaries, and scorecards
  via the public REST API. Use when the user asks to integrate with Demodesk,
  list or download recordings, fetch transcripts, get summaries/scorecards,
  upload external recordings, or build automations on Demodesk meeting data.
metadata:
  author: demodesk
  version: 1.0.0
---

# Demodesk API

## Setup — Get Your API Key

Check `.env` for `DEMODESK_API_KEY`. If missing, follow one of the options below.

### Option A: Auto-extract (if logged into Demodesk in browser)

Extract the API key from a logged-in Demodesk browser session. Falls back to Option B if cookie extraction fails.

```bash
# 1. Get session cookie from Chrome cookie store (macOS)
SESSION_COOKIE=$(sqlite3 "$HOME/Library/Application Support/Google/Chrome/Default/Cookies" \
  "SELECT encrypted_value FROM cookies WHERE host_key='.demodesk.com' AND name='_demodesk_session'" 2>/dev/null)

# 2. Exchange session cookie for Bearer token
BEARER=$(curl -s -X POST "https://demodesk.com/api/v1/users/login" \
  -H "Cookie: _demodesk_session=$SESSION_COOKIE" \
  | python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin)['meta']['access_token']).decode())")

# 3. Extract auth headers from decoded Bearer bundle
ACCESS_TOKEN=$(echo "$BEARER" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['access_token'])")
CLIENT=$(echo "$BEARER" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['client'])")
UID_VAL=$(echo "$BEARER" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['uid'])")

# 4. Fetch existing API key (or generate new one)
API_KEY=$(curl -s "https://demodesk.com/api/v1/users/show_api_key" \
  -H "access-token: $ACCESS_TOKEN" -H "client: $CLIENT" -H "uid: $UID_VAL" -H "token-type: Bearer" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['api_key'])")

# 5. Save to .env
echo "export DEMODESK_API_KEY=$API_KEY" >> .env
grep -qxF '.env' .gitignore 2>/dev/null || echo '.env' >> .gitignore
```

Note: Chrome cookie decryption is OS-dependent (macOS uses Keychain, Linux uses DPAPI/kwallet). Attempt this best-effort and fall back to Option B on failure.

### Option B: Copy from settings

1. Open https://demodesk.com/manage/settings/internet-accounts#:~:text=API%20Key
2. Click the eye icon to reveal the key (or "Generate API Key" if you're an admin)
3. Save it:
```bash
echo 'export DEMODESK_API_KEY=paste-your-key-here' >> .env
grep -qxF '.env' .gitignore 2>/dev/null || echo '.env' >> .gitignore
```

### Option C: Manual navigation

Settings > Integrations > Connections > scroll to the API Key section.

### Important

- The `export` keyword is required in `.env` — without it, `source .env` sets the variable but subprocesses (like `curl`) won't see it.
- Always add `.env` to `.gitignore` if not already there.
- The API key inherits permissions from the user who created it. Admin keys have company-wide access.

## Loading the Key

```bash
source .env && curl -s -H "api-key: $DEMODESK_API_KEY" "https://demodesk.com/api/v2/recordings?limit=5"
```

Always verify the key is non-empty after sourcing: `[ -z "$DEMODESK_API_KEY" ] && echo "Key missing" && exit 1`

## Known Gotchas

1. **Rate limit errors return `{"error":{"code":"invalid_api_key"}}` not 429.** If the key worked before, wait 60 seconds and retry. Don't assume the key is wrong.
2. **Filter brackets need URL-encoding:** use `%5B` / `%5D` instead of `[` / `]`.
3. **Always capture response to `$RESP` before piping to jq** — error responses crash jq.
4. **No `/users` endpoint** — get the meeting owner via the recording detail endpoint and filter for `permission:"host"` in the participants array.

## Live API Reference

For endpoint schemas, filters, and response fields, fetch the live OpenAPI spec:

```bash
curl -s https://demodesk.com/api/docs/v2/generated.yaml | head -500
```

Never rely on memorized schemas — always check the live spec for current field names, filter parameters, enum values, and rate limits.

## Quick Reference (paths only)

| Endpoint          | Method | Path                                    |
|-------------------|--------|-----------------------------------------|
| List recordings   | GET    | /api/v2/recordings                      |
| Get recording     | GET    | /api/v2/recordings/{token}              |
| Get transcript    | GET    | /api/v2/recordings/{token}/transcript   |
| Batch transcripts | POST   | /api/v2/transcripts/batch               |
| List summaries    | GET    | /api/v2/recordings/{token}/summaries    |
| List scorecards   | GET    | /api/v2/recordings/{token}/scorecards   |
| Upload recording  | POST   | /api/v1/externally_recorded_demos       |

## Base URLs

- **V2:** `https://demodesk.com/api/v2`
- **V1:** `https://demodesk.com/api/v1`

## Pagination

Cursor-based. `limit` accepts 1–100 (default 25).
Check `meta.hasNext` in the response — if `true`, pass `meta.nextCursor` as the `cursor=` query parameter.

## Recipes

### 1. List recent ready recordings (last 7 days)

```bash
source .env
RESP=$(curl -s -H "api-key: $DEMODESK_API_KEY" \
  "https://demodesk.com/api/v2/recordings?limit=100&filter%5Bstatus%5D=ready&filter%5BcreatedAfter%5D=$(date -v-7d +%Y-%m-%d)")
echo "$RESP" | jq '.data[] | {token, title, createdAt, duration}'
```

### 2. Bulk download all recordings

```bash
source .env
CURSOR=""
while true; do
  RESP=$(curl -s -H "api-key: $DEMODESK_API_KEY" \
    "https://demodesk.com/api/v2/recordings?limit=100&filter%5Bstatus%5D=ready${CURSOR:+&cursor=$CURSOR}")
  echo "$RESP" | jq -r '.data[] | .token' | while read -r TOKEN; do
    DETAIL=$(curl -s -H "api-key: $DEMODESK_API_KEY" \
      "https://demodesk.com/api/v2/recordings/$TOKEN")
    URL=$(echo "$DETAIL" | jq -r '.temporaryDirectUrl // empty')
    [ -n "$URL" ] && wget -q -O "${TOKEN}.mp4" "$URL"
    sleep 1  # rate-limit aware
  done
  HAS_NEXT=$(echo "$RESP" | jq -r '.meta.hasNext')
  [ "$HAS_NEXT" != "true" ] && break
  CURSOR=$(echo "$RESP" | jq -r '.meta.nextCursor')
done
```

### 3. Get transcript (plaintext + translated)

```bash
source .env
# English plaintext
RESP=$(curl -s -H "api-key: $DEMODESK_API_KEY" \
  "https://demodesk.com/api/v2/recordings/TOKEN/transcript?format=plaintext")
echo "$RESP" | jq -r '.text'

# German translation
RESP=$(curl -s -H "api-key: $DEMODESK_API_KEY" \
  "https://demodesk.com/api/v2/recordings/TOKEN/transcript?format=plaintext&lang=de")
echo "$RESP" | jq -r '.text'
```

Note: If the transcript is still processing, the API returns 202 — wait and retry.

### 4. Batch fetch transcripts (up to 100 tokens)

```bash
source .env
RESP=$(curl -s -X POST -H "api-key: $DEMODESK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"recordingTokens": ["token1", "token2", "token3"]}' \
  "https://demodesk.com/api/v2/transcripts/batch")
echo "$RESP" | jq '.data'
```

### 5. Get meeting owner name

```bash
source .env
RESP=$(curl -s -H "api-key: $DEMODESK_API_KEY" \
  "https://demodesk.com/api/v2/recordings/TOKEN")
echo "$RESP" | jq '.participants[] | select(.permission == "host") | .name'
```

## V1: Upload External Recordings

```bash
source .env
curl -s -X POST -H "api-key: $DEMODESK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "External Call",
    "recording_url": "https://example.com/recording.mp4",
    "scheduled_at": "2025-01-15T10:00:00Z"
  }' \
  "https://demodesk.com/api/v1/externally_recorded_demos"
```

## Webhooks

Not self-serve. Email support@demodesk.com with your endpoint URL.

Available events: `demo.scheduled`, `demo.rescheduled`, `demo.canceled`, `demo.started`, `demo.ended`, `recording.uploaded`, `recording.transcription_postprocessed`, and more.

## Workflow for AI Assistants

1. Check `.env` for `DEMODESK_API_KEY` — if missing, run the setup flow above.
2. `source .env` and verify the key is non-empty.
3. Fetch the live OpenAPI spec if you need schema details for fields, filters, or enums.
4. Use `curl -s` and capture the response to a `$RESP` variable first.
5. Check for errors before piping through `jq`.
6. Use `limit=100` and filter client-side when possible (rate-limit aware).
7. URL-encode filter brackets (`%5B` / `%5D`).
8. Handle rate limits: `invalid_api_key` error but key worked before → wait 60 seconds.
9. Handle 202 responses (transcript still processing) → wait and retry.
10. Handle pagination: check `meta.hasNext`, use `meta.nextCursor`.
