External API for accessing and creating data in your Zudo workspace programmatically.

All endpoints require authentication via the x-api-key header.
Include your API key with every request: x-api-key: psk_your_key_here

Getting Started

Base URL

All API requests should be made to:
https://zudo.so/api/v1

Authentication

Every request must include your API key in the x-api-key header.
Keys start with psk_ and can be generated from your Settings → API Key page.
Example: x-api-key: psk_your_key_here

Rate Limits

Requests are rate-limited per API key using a 1-minute sliding window (default: 100 requests/min).
Rate limit status is returned in response headers:
  • X-RateLimit-Limit — your per-minute quota
  • X-RateLimit-Remaining — requests remaining in the current window
  • X-RateLimit-Reset — seconds until the window resets
When the limit is exceeded, the API returns 429 Too Many Requests.

Pagination

All list endpoints return paginated results. Use page and pageSize query parameters to navigate.
pageSize defaults to 25 and the maximum is 100. Requesting a page beyond totalPages returns an empty data array.
Responses include a pagination object:
{
  "page": 1,
  "pageSize": 25,
  "total": 142,
  "totalPages": 6,
  "hasNextPage": true,
  "hasPrevPage": false
}

Errors

Errors return a JSON body with an error string field.
Common status codes:
  • 400 — Validation error: { "error": "title is required" }
  • 401 — Unauthorized: { "error": "Invalid API key" }
  • 403 — Forbidden: { "error": "Forbidden" }
  • 404 — Not found: { "error": "Account not found" }
  • 405 — Method not allowed: { "error": "Method not allowed" }
  • 429 — Rate limited: { "error": "Rate limit exceeded" }
  • 500 — Server error: { "error": "Internal server error" }

Supported Methods

The API supports GET (read), POST (create), and PATCH for /accounts/{accountId} and /contacts/{contactId} activity updates.
Other PUT, PATCH, and DELETE requests will return 405 Method Not Allowed.
For any other updates or deletes, use the Zudo web application.

Timestamps

All timestamps are returned in ISO 8601 format with UTC timezone: 2025-01-15T12:00:00.000Z
Timestamps you send in request bodies (e.g. dueAt, startAt) should also be ISO 8601 unless otherwise noted. contactActivityAt and lastSeenAt also accept Unix timestamps in seconds or milliseconds. The API normalizes dueAt to noon UTC on the given date.

Enums & Status Values

Task status: OPEN IN_PROGRESS DONE CANCELLED
Task priority: LOW MEDIUM HIGH URGENT
Request status: Open Done
Account status (filter): active onboarding churned inactive
Issue state: synced from your issue tracker (e.g. Backlog, In Progress, Done)

Filtering & Sorting

List endpoints accept query parameters for filtering and sorting. Filters are combined with AND logic.
  • Accounts: search (partial name match), status, sortBy, sortOrder
  • Requests: search, status, minSeverity / maxSeverity, accountId, sortBy, sortOrder
  • Tasks: search, status, priority, sortBy, sortOrder
  • Meetings: search, accountId, sortBy, sortOrder
  • Issues: search, state, sortBy, sortOrder
See each endpoint's query parameters for the full list of accepted values.

Relationships

Resources are connected through many-to-many relationships:
  • Accounts have contacts, meetings, and requests
  • Requests link to accounts and issues
  • Tasks link to accounts, issues, requests, and meetings
  • Issues link to requests (and accounts are deduced from those requests)
Detail endpoints (GET by ID) return nested related objects. List endpoints return a flat summary.

Idempotency

POST endpoints are not idempotent — repeated identical requests will create duplicate resources.
The API does not currently support an Idempotency-Key header. Ensure your integration guards against retries that could create duplicates.

Versioning

The current API version is v1, embedded in the URL path (/api/v1/).
Non-breaking changes (new fields, new endpoints) may be added without a version bump. Breaking changes will be released under a new version prefix with advance notice.

Import into API Tools

The full OpenAPI 3.0 specification is available as JSON:

Postman: File → Import → paste the URL above or upload the downloaded JSON file.
Insomnia: Application → Import/Export → Import Data → From URL or File.
HTTPie: Import the spec from the URL or file to generate a collection.
You can also use the download link in the header to save the file directly.

Webhooks

Webhook support is coming soon. You will be able to subscribe to events like account creation, request status changes, and task updates.
In the meantime, use polling against list endpoints with sortBy=updatedAt&sortOrder=desc to detect recent changes.

Accounts

Customer accounts

GET /accounts
List accounts
Query Parameters
page integer optional query
default: 1
pageSize integer optional query
default: 25
search string optional query
Search by account name or externalId (case-insensitive substring match)
externalId string optional query
Filter to the account with this exact externalId.
status string optional query
active | onboarding | churned | inactive
sortBy string optional query
name | mrr | createdAt | updatedAtdefault: name
sortOrder string optional query
asc | descdefault: asc
Responses
200Paginated list of accounts
401Unauthorized
curl https://zudo.so/api/v1/accounts \
  -H "x-api-key: psk_your_key_here"
const res = await fetch("https://zudo.so/api/v1/accounts", {
  headers: { "x-api-key": "psk_your_key_here" }
});
const data = await res.json();
import requests

res = requests.get("https://zudo.so/api/v1/accounts",
    headers={"x-api-key": "psk_your_key_here"})
data = res.json()
Response
{
  "data": [
    {
      "id": 1,
      "name": "Pied Piper",
      "externalId": "piedpiper",
      "mrr": 48000,
      "active": true,
      "churned": false,
      "onboarding": false,
      "logoUrl": null,
      "createdAt": "2024-03-15T08:00:00.000Z",
      "updatedAt": "2025-01-10T14:22:00.000Z"
    },
    {
      "id": 2,
      "name": "Hooli",
      "externalId": "hooli",
      "mrr": 250000,
      "active": true,
      "churned": false,
      "onboarding": false,
      "logoUrl": null,
      "createdAt": "2024-01-08T10:30:00.000Z",
      "updatedAt": "2025-01-12T09:15:00.000Z"
    },
    {
      "id": 3,
      "name": "Raviga Capital",
      "externalId": "raviga",
      "mrr": 12000,
      "active": true,
      "churned": false,
      "onboarding": false,
      "logoUrl": null,
      "createdAt": "2024-06-20T16:00:00.000Z",
      "updatedAt": "2025-01-05T11:30:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "total": 3,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPrevPage": false
  }
}
POST /accounts
Create account
Request Body
name string required body
Account name
externalId string | null optional body
Your unique identifier for this account. Used to match incoming product-usage events. Must be unique within your organization.
mrr number | null optional body
Monthly recurring revenue
status string optional body
active | onboarding | churneddefault: activeAccount status
contactName string | null optional body
Optionally create a primary contact
contactEmail string | null optional body
Primary contact email (required if contactName is provided)
Responses
201Account created
400Validation error
curl -X POST https://zudo.so/api/v1/accounts \
  -H "x-api-key: psk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Bachmanity Insanity",
    "externalId": "bachmanity",
    "mrr": 15000,
    "status": "active",
    "contactName": "Erlich Bachman",
    "contactEmail": "[email protected]"
  }'
const res = await fetch("https://zudo.so/api/v1/accounts", {
  method: "POST",
  headers: {
    "x-api-key": "psk_your_key_here",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "name": "Bachmanity Insanity",
    "externalId": "bachmanity",
    "mrr": 15000,
    "status": "active",
    "contactName": "Erlich Bachman",
    "contactEmail": "[email protected]"
  })
});
const data = await res.json();
import requests

res = requests.post("https://zudo.so/api/v1/accounts",
    headers={"x-api-key": "psk_your_key_here"},
    json={
    "name": "Bachmanity Insanity",
    "externalId": "bachmanity",
    "mrr": 15000,
    "status": "active",
    "contactName": "Erlich Bachman",
    "contactEmail": "[email protected]"
  })
data = res.json()
Response
{
  "data": {
    "id": 4,
    "name": "Bachmanity Insanity",
    "externalId": "bachmanity",
    "mrr": 15000,
    "active": true,
    "churned": false,
    "onboarding": false,
    "logoUrl": null,
    "createdAt": "2025-01-15T12:00:00.000Z",
    "updatedAt": "2025-01-15T12:00:00.000Z"
  }
}
GET /accounts/{accountId}
Get account by ID
Path Parameters
accountId integer required path
Responses
200Account details with contacts, recent meetings, and linked requests
404Account not found
curl https://zudo.so/api/v1/accounts/1 \
  -H "x-api-key: psk_your_key_here"
const res = await fetch("https://zudo.so/api/v1/accounts/1", {
  headers: { "x-api-key": "psk_your_key_here" }
});
const data = await res.json();
import requests

res = requests.get("https://zudo.so/api/v1/accounts/1",
    headers={"x-api-key": "psk_your_key_here"})
data = res.json()
Response
{
  "data": {
    "id": 1,
    "name": "Pied Piper",
    "externalId": "piedpiper",
    "mrr": 48000,
    "active": true,
    "churned": false,
    "onboarding": false,
    "logoUrl": null,
    "createdAt": "2024-03-15T08:00:00.000Z",
    "updatedAt": "2025-01-10T14:22:00.000Z",
    "contacts": [
      {
        "id": 1,
        "name": "Richard Hendricks",
        "email": "[email protected]"
      },
      {
        "id": 4,
        "name": "Dinesh Chugtai",
        "email": "[email protected]"
      }
    ],
    "meetings": [
      {
        "id": 1,
        "title": "Pied Piper Q1 Business Review",
        "startAt": "2025-01-20T17:00:00.000Z",
        "endAt": "2025-01-20T18:00:00.000Z"
      }
    ],
    "requests": [
      {
        "id": 1,
        "title": "Middle-out compression API endpoint",
        "status": "Open",
        "severity": 4,
        "requestedAt": "2024-11-02T09:00:00.000Z"
      }
    ]
  }
}
PATCH /accounts/{accountId}
Update account
Path Parameters
accountId integer required path
Request Body
externalId string | null optional body
Your unique identifier for this account. Used to match incoming product-usage events. Must be unique within your organization. Set to null or empty string to clear.
contactActivityAt string | integer | null optional body
ISO 8601 timestamp or Unix timestamp in seconds or milliseconds of when a contact was last active. Used as a health score factor. Set to null to clear. Cannot be in the future.
Responses
200Updated account
400Invalid input
404Account not found
curl -X PATCH https://zudo.so/api/v1/accounts/1 \
  -H "x-api-key: psk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "piedpiper",
    "contactActivityAt": 1744113600
  }'
const res = await fetch("https://zudo.so/api/v1/accounts/1", {
  method: "PATCH",
  headers: {
    "x-api-key": "psk_your_key_here",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "externalId": "piedpiper",
    "contactActivityAt": 1744113600
  })
});
const data = await res.json();
import requests

res = requests.patch("https://zudo.so/api/v1/accounts/1",
    headers={"x-api-key": "psk_your_key_here"},
    json={
    "externalId": "piedpiper",
    "contactActivityAt": 1744113600
  })
data = res.json()
Response
{
  "data": {
    "id": 1,
    "name": "Pied Piper",
    "externalId": "piedpiper",
    "mrr": 48000,
    "active": true,
    "churned": false,
    "onboarding": false,
    "contactActivityAt": "2025-04-08T12:00:00.000Z",
    "createdAt": "2024-03-15T08:00:00.000Z",
    "updatedAt": "2025-04-08T12:00:00.000Z"
  }
}

Requests

Customer requests / feature requests

GET /requests
List requests
Query Parameters
page integer optional query
default: 1
pageSize integer optional query
default: 25
search string optional query
Search by title
status string optional query
minSeverity integer optional query
Minimum computed impact score filter applied to the legacy `severity` field (0-100).
maxSeverity integer optional query
Maximum computed impact score filter applied to the legacy `severity` field (0-100).
sortBy string optional query
title | status | severity | requestedAt | updatedAt | createdAtdefault: requestedAtSort by a request field. `severity` sorts by the computed impact score stored in the legacy field.
sortOrder string optional query
asc | descdefault: desc
Responses
200Paginated list of requests
curl https://zudo.so/api/v1/requests \
  -H "x-api-key: psk_your_key_here"
const res = await fetch("https://zudo.so/api/v1/requests", {
  headers: { "x-api-key": "psk_your_key_here" }
});
const data = await res.json();
import requests

res = requests.get("https://zudo.so/api/v1/requests",
    headers={"x-api-key": "psk_your_key_here"})
data = res.json()
Response
{
  "data": [
    {
      "id": 1,
      "title": "Middle-out compression API endpoint",
      "description": "Need a public API to expose the middle-out compression algorithm for third-party integrations.",
      "status": "Open",
      "severity": 4,
      "requestedAt": "2024-11-02T09:00:00.000Z",
      "createdAt": "2024-11-02T09:00:00.000Z",
      "updatedAt": "2025-01-08T16:45:00.000Z",
      "accounts": [
        {
          "id": 1,
          "name": "Pied Piper"
        }
      ],
      "issues": [
        {
          "id": 10,
          "identifier": "ENG-142",
          "title": "Implement compression API",
          "state": "In Progress"
        }
      ]
    },
    {
      "id": 2,
      "title": "Nucleus platform SSO integration",
      "description": "Hooli Nucleus team needs SAML SSO support for enterprise rollout.",
      "status": "Open",
      "severity": 3,
      "requestedAt": "2024-12-10T14:00:00.000Z",
      "createdAt": "2024-12-10T14:00:00.000Z",
      "updatedAt": "2025-01-11T10:00:00.000Z",
      "accounts": [
        {
          "id": 2,
          "name": "Hooli"
        }
      ],
      "issues": []
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "total": 2,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPrevPage": false
  }
}
POST /requests
Create request
Request Body
title string required body
Request title
notes string | null optional body
Additional notes or context
severity integer optional body
default: 1Customer urgency input (1-5). On create, this request-body field is stored as `userSeverity`.
requestedAt string optional body
When the request was made (defaults to now)
accountIds integer[] optional body
Account IDs to link
issueIds integer[] optional body
Issue IDs to link
Responses
201Request created
400Validation error
curl -X POST https://zudo.so/api/v1/requests \
  -H "x-api-key: psk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Weissman score dashboard widget",
    "notes": "Gavin wants a real-time Weissman score displayed on the Hooli dashboard.",
    "severity": 3,
    "accountIds": [2]
  }'
const res = await fetch("https://zudo.so/api/v1/requests", {
  method: "POST",
  headers: {
    "x-api-key": "psk_your_key_here",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "title": "Weissman score dashboard widget",
    "notes": "Gavin wants a real-time Weissman score displayed on the Hooli dashboard.",
    "severity": 3,
    "accountIds": [2]
  })
});
const data = await res.json();
import requests

res = requests.post("https://zudo.so/api/v1/requests",
    headers={"x-api-key": "psk_your_key_here"},
    json={
    "title": "Weissman score dashboard widget",
    "notes": "Gavin wants a real-time Weissman score displayed on the Hooli dashboard.",
    "severity": 3,
    "accountIds": [2]
  })
data = res.json()
Response
{
  "data": {
    "id": 3,
    "title": "Weissman score dashboard widget",
    "description": null,
    "status": "Open",
    "severity": 3,
    "requestedAt": "2025-01-15T12:00:00.000Z",
    "createdAt": "2025-01-15T12:00:00.000Z",
    "updatedAt": "2025-01-15T12:00:00.000Z",
    "accounts": [
      {
        "id": 2,
        "name": "Hooli"
      }
    ],
    "issues": []
  }
}
GET /requests/{requestId}
Get request by ID
Path Parameters
requestId integer required path
Responses
200Request details with linked accounts and issues
404Request not found
curl https://zudo.so/api/v1/requests/1 \
  -H "x-api-key: psk_your_key_here"
const res = await fetch("https://zudo.so/api/v1/requests/1", {
  headers: { "x-api-key": "psk_your_key_here" }
});
const data = await res.json();
import requests

res = requests.get("https://zudo.so/api/v1/requests/1",
    headers={"x-api-key": "psk_your_key_here"})
data = res.json()
Response
{
  "data": {
    "id": 1,
    "title": "Middle-out compression API endpoint",
    "description": "Need a public API to expose the middle-out compression algorithm for third-party integrations.",
    "status": "Open",
    "severity": 4,
    "requestedAt": "2024-11-02T09:00:00.000Z",
    "createdAt": "2024-11-02T09:00:00.000Z",
    "updatedAt": "2025-01-08T16:45:00.000Z",
    "accounts": [
      {
        "id": 1,
        "name": "Pied Piper"
      }
    ],
    "issues": [
      {
        "id": 10,
        "identifier": "ENG-142",
        "title": "Implement compression API",
        "state": "In Progress"
      }
    ]
  }
}
GET /requests/{requestId}/similar-accounts
Find accounts likely interested in this request
Path Parameters
requestId integer required path
Query Parameters
limit integer optional query
default: 8Max candidates to return.
Responses
200Ranked similar accounts with reasoning.
400Request has no linked accounts yet.
403AI features are disabled for this organization.
404Request not found.

Meetings

Meeting records and transcripts

GET /meetings
List meetings
Query Parameters
page integer optional query
default: 1
pageSize integer optional query
default: 25
search string optional query
Case-insensitive substring match against title, gist, overview, and transcript text. Transcript hits are returned with a `transcriptMatch` snippet.
from string optional query
Meetings starting after this date
to string optional query
Meetings starting before this date
sortOrder string optional query
asc | descdefault: desc
Responses
200Paginated list of meetings
curl https://zudo.so/api/v1/meetings?search=compression \
  -H "x-api-key: psk_your_key_here"
const res = await fetch("https://zudo.so/api/v1/meetings?search=compression", {
  headers: { "x-api-key": "psk_your_key_here" }
});
const data = await res.json();
import requests

res = requests.get("https://zudo.so/api/v1/meetings?search=compression",
    headers={"x-api-key": "psk_your_key_here"})
data = res.json()
Response
{
  "data": [
    {
      "id": 1,
      "title": "Pied Piper Q1 Business Review",
      "startAt": "2025-01-20T17:00:00.000Z",
      "endAt": "2025-01-20T18:00:00.000Z",
      "overview": "Discussed platform adoption metrics and the new decentralized internet initiative.",
      "topicsDiscussed": "Compression benchmarks, new internet architecture, Series C timeline",
      "transcriptMatch": "…Richard: our compression scores are still ahead of Hooli but the gap is narrowing. We need to ship the new encoder before…",
      "account": {
        "id": 1,
        "name": "Pied Piper"
      }
    },
    {
      "id": 2,
      "title": "Hooli-XYZ Partnership Sync",
      "startAt": "2025-01-18T15:00:00.000Z",
      "endAt": "2025-01-18T15:45:00.000Z",
      "overview": "Gavin Belson's team wants to explore a deeper integration with the Nucleus platform.",
      "topicsDiscussed": "API limits, data residency, Nucleus roadmap",
      "transcriptMatch": null,
      "account": {
        "id": 2,
        "name": "Hooli"
      }
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "total": 2,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPrevPage": false
  }
}
POST /meetings
Create meeting
Request Body
title string required body
Meeting title
startAt string | null optional body
Meeting start time
endAt string | null optional body
Meeting end time
overview string | null optional body
Meeting overview or summary
topicsDiscussed string | null optional body
Topics discussed
gist string | null optional body
Brief meeting gist
transcriptUrl string | null optional body
URL to transcript
sentences Sentence[] | null optional body
Optional structured transcript. When provided, `transcriptText` is derived automatically and the meeting becomes searchable by its content.
accountId integer optional body
Account ID to link the meeting to
attendees object[] optional body
Meeting attendees
Responses
201Meeting created
400Validation error
curl -X POST https://zudo.so/api/v1/meetings \
  -H "x-api-key: psk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Raviga Board Meeting",
    "startAt": "2025-02-01T18:00:00.000Z",
    "endAt": "2025-02-01T19:00:00.000Z",
    "overview": "Laurie Bream presented the new fund allocation strategy.",
    "accountId": 3,
    "attendees": [
      { "email": "[email protected]", "name": "Laurie Bream" },
      { "email": "[email protected]", "name": "Monica Hall" }
    ],
    "sentences": [
      { "text": "Welcome everyone, let us start with the fund update.", "speaker_name": "Laurie Bream", "speaker_id": 1, "start_time": 0 },
      { "text": "We exited two positions this quarter.", "speaker_name": "Monica Hall", "speaker_id": 2, "start_time": 18.5 }
    ]
  }'
const res = await fetch("https://zudo.so/api/v1/meetings", {
  method: "POST",
  headers: {
    "x-api-key": "psk_your_key_here",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "title": "Raviga Board Meeting",
    "startAt": "2025-02-01T18:00:00.000Z",
    "endAt": "2025-02-01T19:00:00.000Z",
    "overview": "Laurie Bream presented the new fund allocation strategy.",
    "accountId": 3,
    "attendees": [
      { "email": "[email protected]", "name": "Laurie Bream" },
      { "email": "[email protected]", "name": "Monica Hall" }
    ],
    "sentences": [
      { "text": "Welcome everyone, let us start with the fund update.", "speaker_name": "Laurie Bream", "speaker_id": 1, "start_time": 0 },
      { "text": "We exited two positions this quarter.", "speaker_name": "Monica Hall", "speaker_id": 2, "start_time": 18.5 }
    ]
  })
});
const data = await res.json();
import requests

res = requests.post("https://zudo.so/api/v1/meetings",
    headers={"x-api-key": "psk_your_key_here"},
    json={
    "title": "Raviga Board Meeting",
    "startAt": "2025-02-01T18:00:00.000Z",
    "endAt": "2025-02-01T19:00:00.000Z",
    "overview": "Laurie Bream presented the new fund allocation strategy.",
    "accountId": 3,
    "attendees": [
      { "email": "[email protected]", "name": "Laurie Bream" },
      { "email": "[email protected]", "name": "Monica Hall" }
    ],
    "sentences": [
      { "text": "Welcome everyone, let us start with the fund update.", "speaker_name": "Laurie Bream", "speaker_id": 1, "start_time": 0 },
      { "text": "We exited two positions this quarter.", "speaker_name": "Monica Hall", "speaker_id": 2, "start_time": 18.5 }
    ]
  })
data = res.json()
Response
{
  "data": {
    "id": 3,
    "title": "Raviga Board Meeting",
    "startAt": "2025-02-01T18:00:00.000Z",
    "endAt": "2025-02-01T19:00:00.000Z",
    "overview": "Laurie Bream presented the new fund allocation strategy.",
    "topicsDiscussed": null,
    "transcriptText": "Welcome everyone, let us start with the fund update. We exited two positions this quarter.",
    "sentences": [
      {
        "text": "Welcome everyone, let us start with the fund update.",
        "speaker_name": "Laurie Bream",
        "speaker_id": 1,
        "start_time": 0
      },
      {
        "text": "We exited two positions this quarter.",
        "speaker_name": "Monica Hall",
        "speaker_id": 2,
        "start_time": 18.5
      }
    ],
    "account": {
      "id": 3,
      "name": "Raviga Capital"
    }
  }
}
GET /meetings/{meetingId}
Get meeting by ID
Path Parameters
meetingId integer required path
Responses
200Meeting details with attendees and duration
404Meeting not found
curl https://zudo.so/api/v1/meetings/1 \
  -H "x-api-key: psk_your_key_here"
const res = await fetch("https://zudo.so/api/v1/meetings/1", {
  headers: { "x-api-key": "psk_your_key_here" }
});
const data = await res.json();
import requests

res = requests.get("https://zudo.so/api/v1/meetings/1",
    headers={"x-api-key": "psk_your_key_here"})
data = res.json()
Response
{
  "data": {
    "id": 1,
    "title": "Pied Piper Q1 Business Review",
    "startAt": "2025-01-20T17:00:00.000Z",
    "endAt": "2025-01-20T18:00:00.000Z",
    "overview": "Discussed platform adoption metrics and the new decentralized internet initiative.",
    "topicsDiscussed": "Compression benchmarks, new internet architecture, Series C timeline",
    "gist": "Reviewed Q1 adoption metrics; agreed to fast-track the decentralized internet POC.",
    "transcriptUrl": "https://app.fireflies.ai/view/abc123",
    "transcriptText": "Richard: our compression scores are still ahead of Hooli but the gap is narrowing. Jared: we need to ship the new encoder before Series C diligence…",
    "sentences": [
      {
        "text": "Our compression scores are still ahead of Hooli but the gap is narrowing.",
        "speaker_name": "Richard Hendricks",
        "speaker_id": 1,
        "start_time": 12.4
      },
      {
        "text": "We need to ship the new encoder before Series C diligence.",
        "speaker_name": "Jared Dunn",
        "speaker_id": 2,
        "start_time": 18.9
      }
    ],
    "durationMinutes": 60,
    "account": {
      "id": 1,
      "name": "Pied Piper"
    },
    "attendees": [
      {
        "id": 1,
        "name": "Richard Hendricks",
        "email": "[email protected]"
      },
      {
        "id": 5,
        "name": "Jared Dunn",
        "email": "[email protected]"
      }
    ]
  }
}

Conversations

Email threads and messages

GET /conversations/threads
List email threads
Query Parameters
page integer optional query
default: 1
pageSize integer optional query
default: 25
search string optional query
Search by subject
accountId integer optional query
Filter by account
sortBy string optional query
subject | latestMessageAt | createdAtdefault: latestMessageAt
sortOrder string optional query
asc | descdefault: desc
Responses
200Paginated list of email threads
curl https://zudo.so/api/v1/conversations/threads \
  -H "x-api-key: psk_your_key_here"
const res = await fetch("https://zudo.so/api/v1/conversations/threads", {
  headers: { "x-api-key": "psk_your_key_here" }
});
const data = await res.json();
import requests

res = requests.get("https://zudo.so/api/v1/conversations/threads",
    headers={"x-api-key": "psk_your_key_here"})
data = res.json()
Response
{
  "data": [
    {
      "id": 1,
      "subject": "Re: Pied Piper platform migration timeline",
      "snippet": "Richard, just following up on the migration window we discussed...",
      "latestMessageDirection": "inbound",
      "latestMessageAt": "2025-01-14T11:30:00.000Z",
      "createdAt": "2025-01-10T09:00:00.000Z",
      "messageCount": 5,
      "account": {
        "id": 1,
        "name": "Pied Piper"
      }
    },
    {
      "id": 2,
      "subject": "Hooli Nucleus - API rate limit increase",
      "snippet": "We need to bump up our rate limits before the Nucleus launch...",
      "latestMessageDirection": "outbound",
      "latestMessageAt": "2025-01-13T16:15:00.000Z",
      "createdAt": "2025-01-12T08:00:00.000Z",
      "messageCount": 3,
      "account": {
        "id": 2,
        "name": "Hooli"
      }
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "total": 2,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPrevPage": false
  }
}
POST /conversations/threads
Create email thread
Request Body
accountId integer required body
Account ID to link the thread to
subject string required body
Email thread subject
body string | null optional body
Body of the initial message
fromEmail string | null optional body
Sender email address
toEmails string[] optional body
Recipient email address(es). Also accepts a single string.
receivedAt string optional body
When the message was received (defaults to now)
Responses
201Thread created with initial message
400Validation error
404Account not found
curl -X POST https://zudo.so/api/v1/conversations/threads \
  -H "x-api-key: psk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": 1,
    "subject": "Decentralized internet POC update",
    "body": "Hey Richard, attached are the latest throughput numbers from the new internet tests.",
    "fromEmail": "[email protected]",
    "toEmails": ["[email protected]"]
  }'
const res = await fetch("https://zudo.so/api/v1/conversations/threads", {
  method: "POST",
  headers: {
    "x-api-key": "psk_your_key_here",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "accountId": 1,
    "subject": "Decentralized internet POC update",
    "body": "Hey Richard, attached are the latest throughput numbers from the new internet tests.",
    "fromEmail": "[email protected]",
    "toEmails": ["[email protected]"]
  })
});
const data = await res.json();
import requests

res = requests.post("https://zudo.so/api/v1/conversations/threads",
    headers={"x-api-key": "psk_your_key_here"},
    json={
    "accountId": 1,
    "subject": "Decentralized internet POC update",
    "body": "Hey Richard, attached are the latest throughput numbers from the new internet tests.",
    "fromEmail": "[email protected]",
    "toEmails": ["[email protected]"]
  })
data = res.json()
Response
{
  "data": {
    "id": 3,
    "subject": "Decentralized internet POC update",
    "snippet": "Hey Richard, attached are the latest throughput numbers from the new internet tests.",
    "latestMessageDirection": "outbound",
    "latestMessageAt": "2025-01-15T12:00:00.000Z",
    "createdAt": "2025-01-15T12:00:00.000Z",
    "messageCount": 1,
    "account": {
      "id": 1,
      "name": "Pied Piper"
    },
    "messages": [
      {
        "id": 3,
        "direction": "outbound",
        "isInternalSender": true,
        "isInternalRecipient": false,
        "fromEmail": "[email protected]",
        "toEmails": [
          "[email protected]"
        ],
        "subject": "Decentralized internet POC update",
        "snippet": "Hey Richard, attached are the latest throughput numbers from the new internet tests.",
        "receivedAt": "2025-01-15T12:00:00.000Z"
      }
    ]
  }
}
GET /conversations/threads/{threadId}/messages
Get messages in a thread
Path Parameters
threadId integer required path
Numeric thread ID from list_email_threads `data[].id`.
Query Parameters
page integer optional query
default: 1
pageSize integer optional query
default: 25
Responses
200Paginated messages in the thread
404Thread not found
curl https://zudo.so/api/v1/conversations/threads/1/messages \
  -H "x-api-key: psk_your_key_here"
const res = await fetch("https://zudo.so/api/v1/conversations/threads/1/messages", {
  headers: { "x-api-key": "psk_your_key_here" }
});
const data = await res.json();
import requests

res = requests.get("https://zudo.so/api/v1/conversations/threads/1/messages",
    headers={"x-api-key": "psk_your_key_here"})
data = res.json()
Response
{
  "data": [
    {
      "id": 1,
      "direction": "inbound",
      "isInternalSender": false,
      "isInternalRecipient": true,
      "fromEmail": "[email protected]",
      "toEmails": [
        "[email protected]"
      ],
      "subject": "Re: Pied Piper platform migration timeline",
      "snippet": "Jared, can we push the migration to next week? Gilfoyle is still setting up the servers.",
      "receivedAt": "2025-01-10T09:00:00.000Z",
      "createdAt": "2025-01-10T09:00:00.000Z"
    },
    {
      "id": 2,
      "direction": "outbound",
      "isInternalSender": true,
      "isInternalRecipient": false,
      "fromEmail": "[email protected]",
      "toEmails": [
        "[email protected]"
      ],
      "subject": "Re: Pied Piper platform migration timeline",
      "snippet": "Absolutely, Richard. I will adjust the timeline and notify the stakeholders.",
      "receivedAt": "2025-01-10T09:45:00.000Z",
      "createdAt": "2025-01-10T09:45:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "total": 2,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPrevPage": false
  }
}

Issues

Linked engineering issues

GET /issues
List issues
Query Parameters
page integer optional query
default: 1
pageSize integer optional query
default: 25
search string optional query
Search by title or identifier
state string optional query
sortBy string optional query
title | state | issueCreatedAt | issueUpdatedAtdefault: issueUpdatedAt
sortOrder string optional query
asc | descdefault: desc
Responses
200Paginated list of issues
curl https://zudo.so/api/v1/issues \
  -H "x-api-key: psk_your_key_here"
const res = await fetch("https://zudo.so/api/v1/issues", {
  headers: { "x-api-key": "psk_your_key_here" }
});
const data = await res.json();
import requests

res = requests.get("https://zudo.so/api/v1/issues",
    headers={"x-api-key": "psk_your_key_here"})
data = res.json()
Response
{
  "data": [
    {
      "id": 10,
      "identifier": "ENG-142",
      "title": "Implement compression API",
      "description": "Expose middle-out compression as a public REST endpoint.",
      "state": "In Progress",
      "creator": "[email protected]",
      "url": "https://linear.app/piedpiper/issue/ENG-142",
      "issueCreatedAt": "2024-11-05T10:00:00.000Z",
      "issueUpdatedAt": "2025-01-08T16:45:00.000Z"
    },
    {
      "id": 11,
      "identifier": "ENG-143",
      "title": "Fix Gilfoyle's crypto-mining daemon conflicts",
      "description": "Server resources are being consumed by an unauthorized mining process.",
      "state": "Backlog",
      "creator": "[email protected]",
      "url": "https://linear.app/piedpiper/issue/ENG-143",
      "issueCreatedAt": "2024-12-01T08:00:00.000Z",
      "issueUpdatedAt": "2024-12-15T14:30:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "total": 2,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPrevPage": false
  }
}
GET /issues/{issueId}
Get issue by ID
Path Parameters
issueId integer required path
Responses
200Issue details with linked requests and accounts
404Issue not found
curl https://zudo.so/api/v1/issues/10 \
  -H "x-api-key: psk_your_key_here"
const res = await fetch("https://zudo.so/api/v1/issues/10", {
  headers: { "x-api-key": "psk_your_key_here" }
});
const data = await res.json();
import requests

res = requests.get("https://zudo.so/api/v1/issues/10",
    headers={"x-api-key": "psk_your_key_here"})
data = res.json()
Response
{
  "data": {
    "id": 10,
    "identifier": "ENG-142",
    "title": "Implement compression API",
    "description": "Expose middle-out compression as a public REST endpoint.",
    "state": "In Progress",
    "creator": "[email protected]",
    "url": "https://linear.app/piedpiper/issue/ENG-142",
    "issueCreatedAt": "2024-11-05T10:00:00.000Z",
    "issueUpdatedAt": "2025-01-08T16:45:00.000Z",
    "updatedAt": "2025-01-08T16:45:00.000Z",
    "requests": [
      {
        "id": 1,
        "title": "Middle-out compression API endpoint",
        "status": "Open"
      }
    ],
    "accounts": [
      {
        "id": 1,
        "name": "Pied Piper"
      }
    ]
  }
}

Contacts

Customer contacts

GET /contacts
List contacts
Query Parameters
page integer optional query
default: 1
pageSize integer optional query
default: 25
search string optional query
Search by name or email
accountId integer optional query
Filter by account
Responses
200Paginated list of contacts
curl https://zudo.so/api/v1/contacts \
  -H "x-api-key: psk_your_key_here"
const res = await fetch("https://zudo.so/api/v1/contacts", {
  headers: { "x-api-key": "psk_your_key_here" }
});
const data = await res.json();
import requests

res = requests.get("https://zudo.so/api/v1/contacts",
    headers={"x-api-key": "psk_your_key_here"})
data = res.json()
Response
{
  "data": [
    {
      "id": 1,
      "name": "Richard Hendricks",
      "email": "[email protected]",
      "createdAt": "2024-03-15T08:00:00.000Z",
      "accounts": [
        {
          "id": 1,
          "name": "Pied Piper"
        }
      ]
    },
    {
      "id": 2,
      "name": "Gavin Belson",
      "email": "[email protected]",
      "createdAt": "2024-01-08T10:30:00.000Z",
      "accounts": [
        {
          "id": 2,
          "name": "Hooli"
        }
      ]
    },
    {
      "id": 3,
      "name": "Monica Hall",
      "email": "[email protected]",
      "createdAt": "2024-06-20T16:00:00.000Z",
      "accounts": [
        {
          "id": 3,
          "name": "Raviga Capital"
        }
      ]
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "total": 3,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPrevPage": false
  }
}
POST /contacts
Create contact
Request Body
name string required body
Contact full name
email string required body
Contact email address
accountIds integer[] required body
Account IDs to link this contact to
Responses
201Contact created
400Validation error
403Account not authorized
curl -X POST https://zudo.so/api/v1/contacts \
  -H "x-api-key: psk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Dinesh Chugtai",
    "email": "[email protected]",
    "accountIds": [1]
  }'
const res = await fetch("https://zudo.so/api/v1/contacts", {
  method: "POST",
  headers: {
    "x-api-key": "psk_your_key_here",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "name": "Dinesh Chugtai",
    "email": "[email protected]",
    "accountIds": [1]
  })
});
const data = await res.json();
import requests

res = requests.post("https://zudo.so/api/v1/contacts",
    headers={"x-api-key": "psk_your_key_here"},
    json={
    "name": "Dinesh Chugtai",
    "email": "[email protected]",
    "accountIds": [1]
  })
data = res.json()
Response
{
  "data": {
    "id": 4,
    "name": "Dinesh Chugtai",
    "email": "[email protected]",
    "createdAt": "2025-01-15T12:00:00.000Z",
    "accounts": [
      {
        "id": 1,
        "name": "Pied Piper"
      }
    ]
  }
}
PATCH /contacts/{contactId}
Update contact
Path Parameters
contactId integer required path
Request Body
lastSeenAt string | integer | null optional body
ISO 8601 timestamp or Unix timestamp in seconds or milliseconds for this contact's last activity. Set to null to clear. Cannot be in the future.
Responses
200Updated contact
400Invalid input
404Contact not found
curl -X PATCH https://zudo.so/api/v1/contacts/42 \
  -H "x-api-key: psk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "lastSeenAt": "2025-04-08T12:00:00.000Z"
  }'
const res = await fetch("https://zudo.so/api/v1/contacts/42", {
  method: "PATCH",
  headers: {
    "x-api-key": "psk_your_key_here",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "lastSeenAt": "2025-04-08T12:00:00.000Z"
  })
});
const data = await res.json();
import requests

res = requests.patch("https://zudo.so/api/v1/contacts/42",
    headers={"x-api-key": "psk_your_key_here"},
    json={
    "lastSeenAt": "2025-04-08T12:00:00.000Z"
  })
data = res.json()
Response
{
  "data": {
    "id": 42,
    "name": "Monica Hall",
    "email": "[email protected]",
    "lastSeenAt": "2025-04-08T12:00:00.000Z",
    "createdAt": "2024-03-15T08:00:00.000Z",
    "updatedAt": "2025-04-08T12:00:00.000Z",
    "accounts": [
      {
        "id": 1,
        "name": "Pied Piper"
      }
    ]
  }
}

Tasks

Tasks and action items

GET /tasks
List tasks
Query Parameters
page integer optional query
default: 1
pageSize integer optional query
default: 25
search string optional query
Search by title
status string optional query
priority string optional query
accountId integer optional query
Filter by linked account
sortBy string optional query
title | status | priority | dueAt | createdAt | updatedAtdefault: createdAt
sortOrder string optional query
asc | descdefault: desc
Responses
200Paginated list of tasks
curl https://zudo.so/api/v1/tasks \
  -H "x-api-key: psk_your_key_here"
const res = await fetch("https://zudo.so/api/v1/tasks", {
  headers: { "x-api-key": "psk_your_key_here" }
});
const data = await res.json();
import requests

res = requests.get("https://zudo.so/api/v1/tasks",
    headers={"x-api-key": "psk_your_key_here"})
data = res.json()
Response
{
  "data": [
    {
      "id": 1,
      "title": "Send updated Weissman scores to Hooli",
      "description": "Gavin's team needs the latest benchmark numbers before the Nucleus launch.",
      "status": "OPEN",
      "priority": "HIGH",
      "dueAt": "2025-01-25T12:00:00.000Z",
      "createdAt": "2025-01-10T09:00:00.000Z",
      "updatedAt": "2025-01-10T09:00:00.000Z"
    },
    {
      "id": 2,
      "title": "Follow up with Raviga on Series C docs",
      "description": "Monica needs the financials by end of week.",
      "status": "OPEN",
      "priority": "URGENT",
      "dueAt": "2025-01-17T12:00:00.000Z",
      "createdAt": "2025-01-12T14:00:00.000Z",
      "updatedAt": "2025-01-13T08:30:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "total": 2,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPrevPage": false
  }
}
POST /tasks
Create task
Request Body
title string required body
Task title
description string | null optional body
Task description
priority string | null optional body
LOW | MEDIUM | HIGH | URGENTTask priority
status string optional body
OPEN | IN_PROGRESS | DONE | CANCELLEDdefault: OPENTask status
dueAt string | null optional body
Due date (normalized to noon UTC)
accountIds integer[] optional body
Account IDs to link
accountUrgencies object optional body
Optional per-account customer urgency overrides keyed by account ID. Omitted accounts inherit the request-level default urgency.
issueIds integer[] optional body
Issue IDs to link
requestIds integer[] optional body
Request IDs to link
Responses
201Task created
400Validation error
curl -X POST https://zudo.so/api/v1/tasks \
  -H "x-api-key: psk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Prepare TechCrunch Disrupt demo",
    "description": "Build the live demo showcasing new internet compression ratios.",
    "priority": "URGENT",
    "status": "OPEN",
    "dueAt": "2025-02-10T12:00:00.000Z",
    "accountIds": [1]
  }'
const res = await fetch("https://zudo.so/api/v1/tasks", {
  method: "POST",
  headers: {
    "x-api-key": "psk_your_key_here",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "title": "Prepare TechCrunch Disrupt demo",
    "description": "Build the live demo showcasing new internet compression ratios.",
    "priority": "URGENT",
    "status": "OPEN",
    "dueAt": "2025-02-10T12:00:00.000Z",
    "accountIds": [1]
  })
});
const data = await res.json();
import requests

res = requests.post("https://zudo.so/api/v1/tasks",
    headers={"x-api-key": "psk_your_key_here"},
    json={
    "title": "Prepare TechCrunch Disrupt demo",
    "description": "Build the live demo showcasing new internet compression ratios.",
    "priority": "URGENT",
    "status": "OPEN",
    "dueAt": "2025-02-10T12:00:00.000Z",
    "accountIds": [1]
  })
data = res.json()
Response
{
  "data": {
    "id": 3,
    "title": "Prepare TechCrunch Disrupt demo",
    "description": "Build the live demo showcasing new internet compression ratios.",
    "status": "OPEN",
    "priority": "URGENT",
    "dueAt": "2025-02-10T12:00:00.000Z",
    "createdAt": "2025-01-15T12:00:00.000Z",
    "updatedAt": "2025-01-15T12:00:00.000Z"
  }
}
GET /tasks/{taskId}
Get task by ID
Path Parameters
taskId integer required path
Responses
200Task details with linked accounts, issues, requests, meetings, and assignee
404Task not found
curl https://zudo.so/api/v1/tasks/1 \
  -H "x-api-key: psk_your_key_here"
const res = await fetch("https://zudo.so/api/v1/tasks/1", {
  headers: { "x-api-key": "psk_your_key_here" }
});
const data = await res.json();
import requests

res = requests.get("https://zudo.so/api/v1/tasks/1",
    headers={"x-api-key": "psk_your_key_here"})
data = res.json()
Response
{
  "data": {
    "id": 1,
    "title": "Send updated Weissman scores to Hooli",
    "description": "Gavin's team needs the latest benchmark numbers before the Nucleus launch.",
    "status": "OPEN",
    "priority": "HIGH",
    "dueAt": "2025-01-25T12:00:00.000Z",
    "createdAt": "2025-01-10T09:00:00.000Z",
    "updatedAt": "2025-01-10T09:00:00.000Z",
    "assignee": {
      "id": 5,
      "fullName": "Jared Dunn"
    },
    "accounts": [
      {
        "id": 2,
        "name": "Hooli"
      }
    ],
    "issues": [
      {
        "id": 10,
        "identifier": "ENG-142",
        "title": "Implement compression API"
      }
    ],
    "requests": [
      {
        "id": 1,
        "title": "Middle-out compression API endpoint",
        "status": "Open"
      }
    ],
    "meetings": [
      {
        "id": 2,
        "title": "Hooli-XYZ Partnership Sync",
        "startAt": "2025-01-18T15:00:00.000Z"
      }
    ]
  }
}

Schemas

Pagination

page integer
pageSize integer
total integer
totalPages integer
hasNextPage boolean
hasPrevPage boolean

Error

error string

Account

id integer
name string
externalId string | null
mrr number | null
active boolean
churned boolean
onboarding boolean
logoUrl string | null
contactActivityAt string | null
createdAt string
updatedAt string

Request

id integer
title string
notes string | null
status string
severity number | null
userSeverity integer | null
effectiveUserSeverity number | null
requestedAt string | null
createdAt string
updatedAt string
accounts object[]
issues object[]

Sentence

text string
speaker_name string | null
speaker_id integer | string | null
start_time number | null

Meeting

id integer
title string
startAt string | null
endAt string | null
overview string | null
topicsDiscussed string | null
gist string | null
transcriptUrl string | null
transcriptText string | null
sentences Sentence[] | null
transcriptMatch string | null
durationMinutes number | null
account object | null
attendees object[]

Thread

id integer
subject string
snippet string | null
latestMessageDirection string
latestMessageAt string | null
createdAt string
messageCount integer
account object | null
messages Message[]

Issue

id integer
identifier string
title string
description string | null
state string
creator string | null
url string | null
issueCreatedAt string | null
issueUpdatedAt string | null
updatedAt string
requests object[]
accounts object[]

Contact

id integer
name string
email string
lastSeenAt string | null
createdAt string
updatedAt string
accounts object[]

Message

id integer
direction string
isInternalSender boolean | null
isInternalRecipient boolean | null
fromEmail string
toEmails string[]
subject string | null
snippet string | null
receivedAt string | null
createdAt string

Task

id integer
title string
description string | null
status string
priority string | null
dueAt string | null
createdAt string
updatedAt string
assignee object | null

CreateAccount

name string
externalId string | null
mrr number | null
status string
contactName string | null
contactEmail string | null

CreateContact

name string
email string
accountIds integer[]

CreateTask

title string
description string | null
priority string | null
status string
dueAt string | null
accountIds integer[]
accountUrgencies object
issueIds integer[]
requestIds integer[]

CreateRequest

title string
notes string | null
severity integer
requestedAt string
accountIds integer[]
issueIds integer[]

CreateMeeting

title string
startAt string | null
endAt string | null
overview string | null
topicsDiscussed string | null
gist string | null
transcriptUrl string | null
sentences Sentence[] | null
accountId integer
attendees object[]

CreateThread

accountId integer
subject string
body string | null
fromEmail string | null
toEmails string[]
receivedAt string