PilotPM developers

API reference

Push signals into PilotPM from your own systems — agent-observed events and customer profile updates — and pull per-account product issues, feature requests, and summary counts into your CS tools. One auth header, an importable OpenAPI 3.1 spec. Only the endpoints on this page are part of the public, versioned API.

OpenAPI 3.1 spec (JSON)https://app.pilotpm.ai

Authentication

Every request authenticates with a per-workspace agent token passed in the x-workspace-agent-token header. A workspace admin creates tokens under Settings → Agent tokens. The raw token (format wsk_…, 256 bits of entropy) is shown once at creation — only its SHA-256 hash is stored — and can be revoked instantly from the same screen.

  • One token = one workspace. The workspace is bound server-side from the token; request bodies never carry a workspace ID, so a leaked token can never reach another tenant.
  • Store the token in a secret manager (or your platform's encrypted env store) — never in source control or client-side code. Requests without a valid, unrevoked token get 401.
  • To rotate: generate a new token, switch your callers over, then revoke the old one. Multiple active tokens per workspace are supported, so rotation is zero-downtime.
  • Scopes. Tokens carry capabilities chosen at mint time: events:write (the write endpoints) and read (the GET /api/v1/accounts endpoints). The default mint has both; mint a read-only token for tools that should never write. Calling an endpoint without the required scope returns 403. Tokens created before scopes existed have full access.

Rate limits & caps

  • POST /api/events: 100 requests per 60-second sliding window per token. Excess requests return 429 with a Retry-After header (seconds) — honor it and retry.
  • POST /api/events bodies are capped at 64 KB (65,536 bytes); larger payloads are rejected with 413 before parsing.
  • Individual field caps (lengths, counts, ranges) are listed per endpoint below and enforced with 422 validation errors on /api/events.
  • GET /api/v1/accounts/…: 60 requests per 60-second sliding window per token (one budget shared across the three account endpoints), same 429 + Retry-After contract. List responses are capped at 50 rows, newest first.
POST

/api/v1/events

Ingest an event

Push a signal observed by an out-of-process agent into the workspace's event stream (themes, competitive intel, delivery risks, call briefs). The target workspace is bound from the token — a workspace_id in the body is ignored, as is any other unknown top-level field. Request bodies are capped at 65536 bytes (64 KB) and rejected with 413 before parsing. Rate limit: the shared per-token budget (60 requests/minute + 5,000/day across every endpoint in this spec) plus the per-workspace aggregate ceiling (2x); excess requests get the standard 429 (Retry-After + X-RateLimit-* headers). Requires the events:write scope. Also reachable at the legacy unversioned path /api/events (identical behavior).

Auth: x-workspace-agent-token header (required).

Request body

FieldTypeRequiredConstraintsDescription
agentenumrequiredintake-analyst | delivery-pm | roadmap-strategist | competitive-researcher | call-briefWhich agent persona produced this event. Must be one of the five built-in personas.
typestringrequired1–64 charsFree-form event type slug, e.g. "theme_spike" or "competitor_launch". Used for grouping and filtering.
severityenumoptionalinfo | watch | alert; default "info"Triage level. Defaults to info if omitted.
titlestringrequired1–500 charsHuman-readable headline for the event.
summarystringoptional≤ 1000 charsOptional longer summary.
dataobjectoptionaldefault {}Structured payload; shape varies per agent. The well-known fields below have enforced caps. Extra keys are accepted (passthrough).
citationsarray<object>required1–50 itemsRequired — every event must cite its sources. At least one citation.
occurred_atstringoptionalISO 8601 UTCWhen the event happened, ISO 8601 UTC with a Z suffix (e.g. "2026-07-04T09:30:00Z"; timezone offsets are rejected). Defaults to receipt time.
data fields
FieldTypeRequiredConstraintsDescription
themestringoptional≤ 200 chars
customer_namestringoptional≤ 200 chars
customer_countintegeroptional0 – 100,000
source_countintegeroptional0 – 100,000
severitystringoptional≤ 40 charsAgent-specific severity label inside the payload (distinct from the top-level severity enum).
competitor_namestringoptional≤ 200 chars
implicationstringoptional≤ 2000 chars
estimated_arr_at_risknumberoptional0 – 1,000,000,000,000,000
citations[] fields
FieldTypeRequiredConstraintsDescription
kindstringrequired1–40 charsSource kind, e.g. "conversation", "slack", "ticket", "url".
urlstringoptional≤ 2048 chars; valid URLLink to the source (must be a valid URL).
tsstringoptional≤ 40 charsSource-native timestamp or message ts.
idstringoptional≤ 200 charsSource-native identifier.

Example request

curl -X POST https://app.pilotpm.ai/api/v1/events \
  -H 'content-type: application/json' \
  -H 'x-workspace-agent-token: wsk_YOUR_TOKEN_HERE' \
  -d '{
  "agent": "intake-analyst",
  "type": "theme_spike",
  "severity": "watch",
  "title": "Login failures spiking for EU users",
  "summary": "14 customers reported OAuth sign-in loops in the last 24 hours.",
  "data": {
    "theme": "authentication",
    "customer_count": 14
  },
  "citations": [
    {
      "kind": "conversation",
      "id": "conv_8f3a"
    }
  ],
  "occurred_at": "2026-07-04T09:30:00Z"
}'

Responses

StatusMeaningExample body
201Event stored.{ "id": "5b1e5f0a-9c1d-4a6f-8f2e-3d7c9a1b2c3d" }
400Body is not valid JSON.{ "error": "invalid JSON" }
401Missing, malformed, revoked, or unknown token.{ "error": "unauthorized" }
403Token authenticated but lacks the events:write scope (read-only tokens cannot write).{ "error": "forbidden", "missing_scope": "events:write" }
404The workspace bound to the token no longer exists (deleted after the token was created).{ "error": "workspace not found" }
413Request body exceeds 65536 bytes (64 KB).{ "error": "body too large (max 65536 bytes)" }
422Body failed schema validation. The issues array lists each violation (Zod issue format: code, path, message).{ "error": "validation failed", "issues": [ { "code": "too_small", "path": [ "citations" ], "message": "citations required — every event must cite its source" } ] }
429Rate limit exceeded. Budgets (fixed windows: minute + UTC day): 60 requests/minute and 5,000 requests/day per token — one budget shared across every endpoint in this spec — plus a per-workspace aggregate ceiling of 2x those numbers across all of the workspace's tokens (minting more tokens does not multiply capacity). The body's scope field names the exhausted budget. Back off for Retry-After seconds; X-RateLimit-Reset is the unix timestamp when the binding window resets.Headers: Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset{ "ok": false, "error": "rate_limited", "scope": "token_minute", "limit": 60, "window": "minute", "retry_after_seconds": 21 }
POST

/api/v1/customers/upsert

Upsert a customer

Idempotent upsert of a customer record, keyed on (workspace, external_id). Call it when an end-user signs in, updates their profile, or changes plan; the resulting attributes render in the inbox side panel for every conversation linked to that customer. Attributes merge shallowly (new keys win, existing keys you don't send are kept). Attribute keys are lowercased; keys longer than 60 chars are dropped; PII-shaped keys (email, name, phone, address) are dropped from attributes — use the dedicated email / name fields instead. Values may be strings (truncated to 1000 chars), numbers, booleans, null, or arrays of primitives (capped at 20 items); nested objects are silently skipped. At most 50 attributes are stored per call. Requires the events:write scope. Also reachable at the legacy unversioned path /api/customers/upsert (identical behavior).

Auth: x-workspace-agent-token header (required).

Request body

FieldTypeRequiredConstraintsDescription
external_idstringrequired1–240 charsYour system's stable ID for this user. Leading/trailing whitespace is trimmed; the trimmed value must be non-empty.
emailstringoptionalOptional but recommended — enables inbound-email matching. Normalized to lowercase; stored encrypted.
namestringoptionalOptional display name. Stored encrypted.
attributesobjectoptionalFlat bag of profile attributes (plan tier, platform, country, renewal risk, …). See the merge and sanitization rules in the endpoint description.

Example request

curl -X POST https://app.pilotpm.ai/api/v1/customers/upsert \
  -H 'content-type: application/json' \
  -H 'x-workspace-agent-token: wsk_YOUR_TOKEN_HERE' \
  -d '{
  "external_id": "user_abc123",
  "email": "alice@example.com",
  "name": "Alice Wonder",
  "attributes": {
    "platform": "iOS",
    "tier": "Premium",
    "country": "Vietnam",
    "os_version": "17.4",
    "renewal_risk": "medium"
  }
}'

Responses

StatusMeaningExample body
200Upsert succeeded. created is true when a new customer row was inserted, false when an existing one was updated.{ "ok": true, "id": "0d7f3b2a-1c4e-4f6a-9b8d-2e5f7a9c1b3d", "created": true }
400Body is not valid JSON (invalid_body), or external_id is missing / empty after trimming (missing_external_id).{ "error": "missing_external_id" }
401Missing, malformed, revoked, or unknown token.{ "error": "unauthorized" }
403Token authenticated but lacks the events:write scope (read-only tokens cannot write).{ "error": "forbidden", "missing_scope": "events:write" }
429Rate limit exceeded. Budgets (fixed windows: minute + UTC day): 60 requests/minute and 5,000 requests/day per token — one budget shared across every endpoint in this spec — plus a per-workspace aggregate ceiling of 2x those numbers across all of the workspace's tokens (minting more tokens does not multiply capacity). The body's scope field names the exhausted budget. Back off for Retry-After seconds; X-RateLimit-Reset is the unix timestamp when the binding window resets.Headers: Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset{ "ok": false, "error": "rate_limited", "scope": "token_minute", "limit": 60, "window": "minute", "retry_after_seconds": 21 }
500Upsert failed server-side (also returned when external_id exceeds 240 characters).{ "error": "upsert_failed" }
GET

/api/v1/accounts/{hubspotCompanyId}/issues

List an account's product issues

The account's product ISSUES: conversations whose latest inbound message the classifier tagged bug_report (the taxonomy's explicit defect bucket — crashes, errors, wrong results). Support/ops categories (billing, login, activation) are deliberately excluded. Accounts are keyed by HubSpot company id — the same key ChurnZero uses as Account External ID. Workspace is bound from the token; a company id belonging to another tenant returns 404 here. Spam and merged-duplicate conversations are excluded. Newest first, capped at 50 rows. Requires the read scope. Rate limit: the shared per-token budget (60 requests/minute + 5,000/day across every endpoint in this spec) plus the per-workspace aggregate ceiling (2x).

Auth: x-workspace-agent-token header (required).

Parameters

NameInRequiredConstraintsDescription
hubspotCompanyIdpathrequired1–64 charsThe account's HubSpot company id — the same value external CS tools (e.g. ChurnZero) use as the Account External ID. Up to 64 chars of [A-Za-z0-9_-]; anything else returns 400 invalid_company_id.
statusqueryoptionalopen | all; default "open"open (default) = conversations not yet resolved/closed (status open, awaiting_customer, or snoozed). all = include resolved and closed too. Unrecognized values fall back to open.

Example request

curl https://app.pilotpm.ai/api/v1/accounts/144818714/issues \
  -H 'x-workspace-agent-token: wsk_YOUR_TOKEN_HERE'

Responses

StatusMeaningExample body
200The account's issue conversations.{ "ok": true, "account": { "hubspot_company_id": "144818714", "logo_id": "org_9f3a21", "logo_name": "Acme University" }, "data": [ { "id": "5b1e5f0a-9c1d-4a6f-8f2e-3d7c9a1b2c3d", "subject": "Scores wrong after the last update", "category": "bug_report", "status": "open", "created_at": "2026-07-02T08:11:24.000Z", "resolved_at": null } ] }
400Malformed hubspotCompanyId path segment.{ "error": "invalid_company_id" }
401Missing, malformed, revoked, or unknown token.{ "error": "unauthorized" }
403Token authenticated but lacks the read scope.{ "error": "forbidden", "missing_scope": "read" }
404Unknown company: this workspace has never had a conversation for that HubSpot company id. (An account whose conversations are all resolved still returns 200 with empty/zero data.){ "error": "unknown_company" }
429Rate limit exceeded. Budgets (fixed windows: minute + UTC day): 60 requests/minute and 5,000 requests/day per token — one budget shared across every endpoint in this spec — plus a per-workspace aggregate ceiling of 2x those numbers across all of the workspace's tokens (minting more tokens does not multiply capacity). The body's scope field names the exhausted budget. Back off for Retry-After seconds; X-RateLimit-Reset is the unix timestamp when the binding window resets.Headers: Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset{ "ok": false, "error": "rate_limited", "scope": "token_minute", "limit": 60, "window": "minute", "retry_after_seconds": 21 }
GET

/api/v1/accounts/{hubspotCompanyId}/requests

List an account's feature requests

The account's feature REQUESTS: conversations whose latest inbound message the classifier tagged feature_or_product. Caveat: that category also covers general product / how-it-works questions (the taxonomy doesn't split them), so treat these as an upper bound on true feature asks. Same keying, auth, exclusions, ordering, cap, status filter, and rate limit as the issues endpoint.

Auth: x-workspace-agent-token header (required).

Parameters

NameInRequiredConstraintsDescription
hubspotCompanyIdpathrequired1–64 charsThe account's HubSpot company id — the same value external CS tools (e.g. ChurnZero) use as the Account External ID. Up to 64 chars of [A-Za-z0-9_-]; anything else returns 400 invalid_company_id.
statusqueryoptionalopen | all; default "open"open (default) = conversations not yet resolved/closed (status open, awaiting_customer, or snoozed). all = include resolved and closed too. Unrecognized values fall back to open.

Example request

curl https://app.pilotpm.ai/api/v1/accounts/144818714/requests \
  -H 'x-workspace-agent-token: wsk_YOUR_TOKEN_HERE'

Responses

StatusMeaningExample body
200The account's request conversations.{ "ok": true, "account": { "hubspot_company_id": "144818714", "logo_id": "org_9f3a21", "logo_name": "Acme University" }, "data": [ { "id": "0d7f3b2a-1c4e-4f6a-9b8d-2e5f7a9c1b3d", "subject": "Can we get an export to CSV?", "category": "feature_or_product", "status": "resolved", "created_at": "2026-06-21T14:03:00.000Z", "resolved_at": "2026-06-23T09:45:12.000Z" } ] }
400Malformed hubspotCompanyId path segment.{ "error": "invalid_company_id" }
401Missing, malformed, revoked, or unknown token.{ "error": "unauthorized" }
403Token authenticated but lacks the read scope.{ "error": "forbidden", "missing_scope": "read" }
404Unknown company: this workspace has never had a conversation for that HubSpot company id. (An account whose conversations are all resolved still returns 200 with empty/zero data.){ "error": "unknown_company" }
429Rate limit exceeded. Budgets (fixed windows: minute + UTC day): 60 requests/minute and 5,000 requests/day per token — one budget shared across every endpoint in this spec — plus a per-workspace aggregate ceiling of 2x those numbers across all of the workspace's tokens (minting more tokens does not multiply capacity). The body's scope field names the exhausted budget. Back off for Retry-After seconds; X-RateLimit-Reset is the unix timestamp when the binding window resets.Headers: Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset{ "ok": false, "error": "rate_limited", "scope": "token_minute", "limit": 60, "window": "minute", "retry_after_seconds": 21 }
GET

/api/v1/accounts/{hubspotCompanyId}/summary

Get an account's counts-only summary

Counts-only rollup for the account, computed in one query: open/total product issues (bug_report), open/total feature requests (feature_or_product), open conversations of any category, and the timestamp of the last message in either direction (last_contact_at). Open = status open, awaiting_customer, or snoozed. Spam and merged-duplicate conversations are excluded from every count. Also carries the org-level usage trio (licenses_purchased, learners_registered, org_utilization_pct) from ELSA's warehouse account mapping — null until the account is mapped. Same keying, auth, and rate limit as the list endpoints.

Auth: x-workspace-agent-token header (required).

Parameters

NameInRequiredConstraintsDescription
hubspotCompanyIdpathrequired1–64 charsThe account's HubSpot company id — the same value external CS tools (e.g. ChurnZero) use as the Account External ID. Up to 64 chars of [A-Za-z0-9_-]; anything else returns 400 invalid_company_id.

Example request

curl https://app.pilotpm.ai/api/v1/accounts/144818714/summary \
  -H 'x-workspace-agent-token: wsk_YOUR_TOKEN_HERE'

Responses

StatusMeaningExample body
200The account's summary counts.{ "ok": true, "account": { "hubspot_company_id": "144818714", "logo_id": "org_9f3a21", "logo_name": "Acme University" }, "data": { "issues_open": 2, "issues_total": 9, "requests_open": 1, "requests_total": 4, "open_conversations": 5, "last_contact_at": "2026-07-03T18:22:41.000Z", "licenses_purchased": 14000, "learners_registered": 15000, "org_utilization_pct": 107.1, "tickets": { "open_issue": { "open": 2, "total": 9 }, "feature_ask": { "open": 1, "total": 4 }, "learner_support": { "open": 2, "total": 6 } } } }
400Malformed hubspotCompanyId path segment.{ "error": "invalid_company_id" }
401Missing, malformed, revoked, or unknown token.{ "error": "unauthorized" }
403Token authenticated but lacks the read scope.{ "error": "forbidden", "missing_scope": "read" }
404Unknown company: this workspace has never had a conversation for that HubSpot company id. (An account whose conversations are all resolved still returns 200 with empty/zero data.){ "error": "unknown_company" }
429Rate limit exceeded. Budgets (fixed windows: minute + UTC day): 60 requests/minute and 5,000 requests/day per token — one budget shared across every endpoint in this spec — plus a per-workspace aggregate ceiling of 2x those numbers across all of the workspace's tokens (minting more tokens does not multiply capacity). The body's scope field names the exhausted budget. Back off for Retry-After seconds; X-RateLimit-Reset is the unix timestamp when the binding window resets.Headers: Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset{ "ok": false, "error": "rate_limited", "scope": "token_minute", "limit": 60, "window": "minute", "retry_after_seconds": 21 }
GET

/api/v1/accounts/{hubspotCompanyId}/tickets

List an account's unified tickets (3 buckets)

The account's UNIFIED tickets across the three CSM-facing buckets: open_issue (product defects — bug_report), feature_ask (feature / how-it-works asks — feature_or_product), and learner_support (the account's day-to-day support: billing, cancellation, login, activation, other). Bucket = the latest inbound message's classifier tag. Accounts are keyed by HubSpot company id; workspace is bound from the token. Spam + merged-duplicate conversations are excluded. Per-row priority is DERIVED (SLA breach / VIP), not a first-class field. Newest first; keyset-paginated via next_cursor. Requires the read scope. Rate limit: 60 requests per 60-second sliding window per token (shared across the account endpoints).

Auth: x-workspace-agent-token header (required).

Parameters

NameInRequiredConstraintsDescription
hubspotCompanyIdpathrequired1–64 charsThe account's HubSpot company id — the same value external CS tools (e.g. ChurnZero) use as the Account External ID. Up to 64 chars of [A-Za-z0-9_-]; anything else returns 400 invalid_company_id.
categoryqueryoptionalopen_issue | feature_ask | learner_supportRestrict to one bucket. Omitted (or unrecognized) = all three. open_issue = product defects (bug_report); feature_ask = feature / how-it-works asks (feature_or_product); learner_support = the account's day-to-day support (billing, cancellation, login, activation, other).
statusqueryoptionalopen | all; default "open"open (default) = conversations not yet resolved/closed (status open, awaiting_customer, or snoozed). all = include resolved and closed too. Unrecognized values fall back to open.
sincequeryoptionalISO 8601 UTCOnly tickets created at or after this ISO-8601 instant. Unparseable values are ignored (no filter).
limitqueryoptional1 – 100; default 25Page size (default 25, capped at 100). Use next_cursor to page further.
cursorqueryoptionalOpaque keyset cursor — pass the next_cursor from the previous page. Omit for the first page.

Example request

curl https://app.pilotpm.ai/api/v1/accounts/144818714/tickets \
  -H 'x-workspace-agent-token: wsk_YOUR_TOKEN_HERE'

Responses

StatusMeaningExample body
200A page of the account's tickets.{ "ok": true, "account": { "hubspot_company_id": "144818714", "logo_id": "org_9f3a21", "logo_name": "Acme University" }, "data": [ { "id": "5b1e5f0a-9c1d-4a6f-8f2e-3d7c9a1b2c3d", "ticket_number": 4821, "subject": "Scores wrong after the last update", "category": "open_issue", "status": "open", "priority": "high", "owner": "Dana Lee", "created_at": "2026-07-02T08:11:24.000Z", "resolved_at": null } ], "next_cursor": "MTc1MTQ0MzA4NDAwMDo1YjFlNWYwYS05YzFkLTRhNmYtOGYyZS0zZDdjOWExYjJjM2Q" }
400Malformed hubspotCompanyId path segment.{ "error": "invalid_company_id" }
401Missing, malformed, revoked, or unknown token.{ "error": "unauthorized" }
403Token authenticated but lacks the read scope.{ "error": "forbidden", "missing_scope": "read" }
404Unknown company: this workspace has never had a conversation for that HubSpot company id. (An account whose conversations are all resolved still returns 200 with empty/zero data.){ "error": "unknown_company" }
429Rate limit exceeded. Budgets (fixed windows: minute + UTC day): 60 requests/minute and 5,000 requests/day per token — one budget shared across every endpoint in this spec — plus a per-workspace aggregate ceiling of 2x those numbers across all of the workspace's tokens (minting more tokens does not multiply capacity). The body's scope field names the exhausted budget. Back off for Retry-After seconds; X-RateLimit-Reset is the unix timestamp when the binding window resets.Headers: Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset{ "ok": false, "error": "rate_limited", "scope": "token_minute", "limit": 60, "window": "minute", "retry_after_seconds": 21 }
GET

/api/v1/accounts/{hubspotCompanyId}/tickets/{conversationId}

Get one ticket's detail + activity timeline

One ticket's detail: the list-row fields plus a PII-scrubbed first-inbound description_excerpt, the improvement-engine change_class (bugfix|feature|null — optional sharpening for feature asks), and an activity timeline (created / status changes / assignment / escalation / resolution) built from the conversation's audit log. Workspace- AND account-scoped: a ticket outside this account/workspace (or spam/merged) returns 404 unknown_ticket. Requires the read scope; same rate limit as the list.

Auth: x-workspace-agent-token header (required).

Parameters

NameInRequiredConstraintsDescription
hubspotCompanyIdpathrequired1–64 charsThe account's HubSpot company id — the same value external CS tools (e.g. ChurnZero) use as the Account External ID. Up to 64 chars of [A-Za-z0-9_-]; anything else returns 400 invalid_company_id.
conversationIdpathrequiredUUIDThe PilotPM conversation (ticket) id — the `id` field from the tickets list. Must be a uuid (else 400 invalid_conversation_id). A ticket that isn't in this account/workspace (or is spam/merged) returns 404.

Example request

curl https://app.pilotpm.ai/api/v1/accounts/144818714/tickets/5b1e5f0a-9c1d-4a6f-8f2e-3d7c9a1b2c3d \
  -H 'x-workspace-agent-token: wsk_YOUR_TOKEN_HERE'

Responses

StatusMeaningExample body
200The ticket detail.{ "ok": true, "account": { "hubspot_company_id": "144818714", "logo_id": "org_9f3a21", "logo_name": "Acme University" }, "data": { "id": "5b1e5f0a-9c1d-4a6f-8f2e-3d7c9a1b2c3d", "ticket_number": 4821, "subject": "Scores wrong after the last update", "category": "open_issue", "status": "open", "priority": "high", "owner": "Dana Lee", "created_at": "2026-07-02T08:11:24.000Z", "resolved_at": null, "description_excerpt": "Since the update my practice scores show 0 even after I finish a lesson. Contact me at [email].", "change_class": null, "timeline": [ { "at": "2026-07-02T08:11:24.000Z", "type": "created", "actor": null, "detail": "Ticket created" }, { "at": "2026-07-02T09:02:10.000Z", "type": "assignment_changed", "actor": "system", "detail": "Assigned to Dana Lee" } ] } }
400Malformed hubspotCompanyId path segment.{ "error": "invalid_company_id" }
401Missing, malformed, revoked, or unknown token.{ "error": "unauthorized" }
403Token authenticated but lacks the read scope.{ "error": "forbidden", "missing_scope": "read" }
404Unknown company: this workspace has never had a conversation for that HubSpot company id. (An account whose conversations are all resolved still returns 200 with empty/zero data.){ "error": "unknown_company" }
429Rate limit exceeded. Budgets (fixed windows: minute + UTC day): 60 requests/minute and 5,000 requests/day per token — one budget shared across every endpoint in this spec — plus a per-workspace aggregate ceiling of 2x those numbers across all of the workspace's tokens (minting more tokens does not multiply capacity). The body's scope field names the exhausted budget. Back off for Retry-After seconds; X-RateLimit-Reset is the unix timestamp when the binding window resets.Headers: Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset{ "ok": false, "error": "rate_limited", "scope": "token_minute", "limit": 60, "window": "minute", "retry_after_seconds": 21 }

Versioning & changelog

The current version is v1 — the endpoints documented on this page. Additive changes (new optional fields, new endpoints) ship within v1 without notice. Breaking changes ship under a new version prefix with its own spec; when that happens, the previous version keeps working for 6 monthsfrom the new version's publication, and both specs stay downloadable from this page.

  • v1

    READ API + token scopes: new GET /api/v1/accounts/{hubspotCompanyId}/issues, /requests, and /summary endpoints for pulling an account's product issues, feature requests, and counts into external CS tools (accounts keyed by HubSpot company id). Agent tokens now carry scopes — events:write (the write endpoints) and read (the new GET endpoints); existing tokens keep full access, and read-only tokens can be minted from Settings → Agent tokens. The write endpoints' canonical paths are now POST /api/v1/events and POST /api/v1/customers/upsert; the unversioned paths remain supported as legacy aliases.

  • v1

    Initial publication: POST /api/events (agent event ingestion) and POST /api/customers/upsert (customer profile upsert), authenticated with per-workspace agent tokens.