PulseCheck API Reference

A live-verified map of PulseCheck's two APIs — the Bearer-token Reporting API and the cookie-authenticated App API — reconstructed by probing production, since PulseCheck ships no official docs.

Verified 2026‑07‑10 against production Base api.pulsecheck.spectrocloud.com Framework Remix + Go

How to read the badges

Live — exercised end-to-end against production with a real key and confirmed.   Observed — route and shape recovered from the app's own client bundle or a captured browser request, but not fully round-tripped here.

The two surfaces

PulseCheck is two systems with two auth models. Which one you use depends entirely on what you're doing — this is the single most important thing to get right.

Reporting API BEARER

api.pulsecheck.spectrocloud.com
  • Read-only analytics
  • Cycles, agent runs, people roster
  • Auth: your pca_/psk_ key → JWT
  • Cannot write check-ins or call Ask AI

App API COOKIE

pulsecheck.spectrocloud.com
  • Everything interactive
  • Create / draft / submit / edit check-ins
  • Ask AI, summaries, projects, org chart
  • Auth: SSO session cookie (Okta/Google/SAML)

⚠ Your rotated key is read-only

The pca_/psk_ key you just rotated authenticates only the Reporting API, which exposes reads (cycles, runs, people). It cannot create, draft, submit, or edit a check-in, cannot read summary text, and cannot call Ask AI — those live on the cookie-authenticated App API. As of this probe there is no API-key path to writing a check-in.

POST/auth/tokenLive#

Exchange your API key pair for a short-lived Bearer JWT. Standard OAuth2 client-credentials grant on api.pulsecheck.spectrocloud.com. The token is an RS256 JWT (issuer pulse-check-token-broker) valid for expires_in seconds.

Form parameters
FieldValue
grant_typerequiredclient_credentials
client_idrequiredYour pca_… id
client_secretrequiredYour psk_… secret
Request
curl -X POST 'https://api.pulsecheck.spectrocloud.com/auth/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode 'client_id=pca_xxxxxxxx' \
  --data-urlencode 'client_secret=psk_xxxxxxxx'
// 200 OK
{
  "access_token": "eyJhbGciOiJSUzI1NiI...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Tip: Basic-auth form also works — curl -u pca_:psk_ … --data-urlencode grant_type=client_credentials.

GET/api/v1/cyclesLive#

List check-in cycles (weekly windows), newest first. Use a cycle_id from here as the cycle for reading or editing a specific week's check-in on the App API.

Query parameters
ParamDescription
page_token optionalPagination cursor from next_page_token.
Request & response
curl 'https://api.pulsecheck.spectrocloud.com/api/v1/cycles' \
  -H "Authorization: Bearer $TOKEN"
{
  "cycles": [
    {
      "cycle_id": "7cb09611-0706-45be-8153-1797c3ef0b9f",
      "start_date": "2026-07-06",
      "end_date": "2026-07-12",
      "run_count": 0
    }
  ],
  "next_page_token": null
}
GET/api/v1/cycles/{cycleId}/runsLive#

List the agent runs for a cycle. A run is an AI aggregation job over the org's check-ins (it reads the org chart + check-ins and writes items). This returns run metadata — status, model, token spend, item counts.

⚠ Summaries note

This does not return the human-readable summary text. The generated summary is only surfaced in the app UI (/home); there is no summary-text endpoint on the Reporting API. This is the closest the read API gets to "summaries" — run metadata, not prose.

Path parameters
ParamDescription
cycleIdrequiredCycle UUID from GET /api/v1/cycles.
Response
curl 'https://api.pulsecheck.spectrocloud.com/api/v1/cycles/CYCLE_ID/runs' \
  -H "Authorization: Bearer $TOKEN"
{
  "cycle_id": "468f19c1-...",
  "runs": [{
    "run_id": "75c78a62-...",
    "status": "succeeded",
    "model": "gpt-5.4",
    "items_written": 370,
    "created_at": "2026-06-29T16:37:52Z",
    "finished_at": "2026-06-29T16:51:44Z",
    "token_usage": { "priced": true, "totals": {…}, "by_model": {…} }
  }],
  "next_page_token": null
}
GET/api/v1/peopleLive#

The org roster: everyone PulseCheck knows about, from the imported org chart.

Response fields
FieldType
person_iduuid
name · work_email · titlestring
department · work_locationstring · nullable
employment_statusstring
curl 'https://api.pulsecheck.spectrocloud.com/api/v1/people' \
  -H "Authorization: Bearer $TOKEN"
GET/my-check-in.dataLive#

On pulsecheck.spectrocloud.com. Loads your check-in for a cycle (Remix single-fetch loader). Point cycle at a past cycle UUID to read a past submission. Without a valid session cookie it returns a SingleFetchRedirect to /sign-in — the reliable "am I authenticated?" tell.

Query parameters
ParamDescription
cyclerequiredCycle (week) UUID. From GET /api/v1/cycles or the page URL.
editrequiredSet to 1 to target the editor.
curl 'https://pulsecheck.spectrocloud.com/my-check-in.data?cycle=CYCLE_ID&edit=1' \
  -H 'Cookie: <YOUR_SESSION_COOKIE>'
POST/my-check-in.dataObserved#

The only write path for check-ins — create, draft, submit, and edit are all this one action. The cycle selects the week; intent chooses draft vs submit. Field shape confirmed from a captured browser submission.

Body — application/x-www-form-urlencoded
FieldDescription
accomplishmentsWhat you did. Rich-text HTML.
upcomingWhat's next. Plain text.
blockersBlockers. Rich-text HTML.
intentrequireddraft saves & stays editable · submit submits.
Examples
curl -X POST 'https://pulsecheck.spectrocloud.com/my-check-in.data?cycle=CYCLE_ID&edit=1' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'Cookie: <YOUR_SESSION_COOKIE>' \
  --data-urlencode 'accomplishments=<p>Shipped edge onboarding.</p>' \
  --data-urlencode 'upcoming=2-node HA validation next.' \
  --data-urlencode 'blockers=<p>AMT firmware on node13.</p>' \
  --data-urlencode 'intent=draft'
curl -X POST 'https://pulsecheck.spectrocloud.com/my-check-in.data?cycle=CYCLE_ID&edit=1' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'Cookie: <YOUR_SESSION_COOKIE>' \
  --data-urlencode 'accomplishments=<p>Shipped edge onboarding.</p>' \
  --data-urlencode 'upcoming=2-node HA validation next.' \
  --data-urlencode 'blockers=<p>None.</p>' \
  --data-urlencode 'intent=submit'
# Same call — just point `cycle` at a PAST cycle_id
# (get past ids from GET /api/v1/cycles)
curl -X POST 'https://pulsecheck.spectrocloud.com/my-check-in.data?cycle=PAST_CYCLE_ID&edit=1' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'Cookie: <YOUR_SESSION_COOKIE>' \
  --data-urlencode 'accomplishments=<p>Revised.</p>' \
  --data-urlencode 'intent=draft'

Getting your session cookie

It's the httpOnly cookie __pulse_session, set at login — copy the Cookie header from your browser dev tools (Network tab → any request → Request Headers), or capture it with a browser script (see Automating the session cookie). It can't be minted with the pca_/psk_ key.

POST/ai/chat/streamObserved#

Backs the app's Ask AI button — the conversational assistant that answers questions about the org. Route confirmed in the client manifest (routes/ai-chat-stream); unauthenticated requests 302-redirect to sign-in (verified). Cookie-authenticated and streaming (Server-Sent-Events style). Rate a response with POST /ai-rating.

⚠ Request body is provisional

The action parses its body server-side; the exact JSON field names weren't round-tripped here. The shape below is a best guess — verify against a real browser request before relying on it.

curl -N -X POST 'https://pulsecheck.spectrocloud.com/ai/chat/stream' \
  -H 'Content-Type: application/json' \
  -H 'Cookie: <YOUR_SESSION_COOKIE>' \
  -d '{"message":"Who owns the edge onboarding project?"}'   # field name provisional
Automating the session cookieVerified#

The App API is cookie-only, and there is no grant that returns a session cookie — SAML/OIDC don't work that way, and PulseCheck exposes no write-scoped service token. This tenant federates to Okta (spectrocloud.okta.com) with Google OIDC as an alternate. So "programmatically" means driving a real browser once and reusing the cookie until it expires.

✓ Confirmed working

The one-time-login flow below was run end-to-end: a Playwright browser completed Okta SSO, captured the __pulse_session cookie, and used it to read my-check-in.data (HTTP 200, authenticated) with no manual cookie copying.

Options, least-painful first
ApproachHowTrade-off
Reuse your Chrome profilePlaywright with your logged-in Chrome profile — SSO completes silently.Zero credential handling. Chrome must not hold a profile lock.
One-time interactive loginLog in by hand once, persist storageState, replay headless.Re-run login when the cookie expires (302 → /sign-in).
Fully headless fresh loginScript Okta username/password + factor.Blocked by MFA (push/WebAuthn can't be scripted).
Ask for a write API tokenRequest one from the PulseCheck team.The proper fix — removes the browser entirely.

⚠ Treat the captured cookie like a password

The session cookie is httpOnly and equivalent to a live login. Don't commit storageState / extracted cookies, keep them chmod 600, and expect a short TTL. A fully-unattended fresh login is not reliable while Okta MFA is enforced.

Example
// login.js — headed; you complete Okta SSO + MFA by hand, once
const { chromium } = require('playwright');
const browser = await chromium.launch({ channel: 'chrome', headless: false });
const ctx = await browser.newContext();
const page = await ctx.newPage();
await page.goto('https://pulsecheck.spectrocloud.com/my-check-in');
await page.waitForURL(/pulsecheck\.spectrocloud\.com\/my-check-in/, { timeout: 180000 });
await ctx.storageState({ path: 'pulse-auth.json' });  // chmod 600 this
await browser.close();
// run.js — headless; replay the saved session
const ctx = await browser.newContext({ storageState: 'pulse-auth.json' });
const api = ctx.request;
// read a week's check-in (loader)
await api.get('https://pulsecheck.spectrocloud.com/my-check-in.data?cycle=CYCLE_ID&edit=1');
// save a draft (action)
await api.post('https://pulsecheck.spectrocloud.com/my-check-in.data?cycle=CYCLE_ID&edit=1', {
  form: { accomplishments: '<p>…</p>', upcoming: '…', blockers: '…', intent: 'draft' }
});
// Reuse an already-authenticated Chrome profile — no login step at all
const ctx = await chromium.launchPersistentContext(
  '~/Library/Application Support/Google/Chrome/Default',
  { channel: 'chrome', headless: false });
// Chrome must be fully quit first (profile lock).
const cookies = await ctx.cookies('https://pulsecheck.spectrocloud.com');
Your questions, answered
You want to…UseAuth
Create / draft / submit a check-inPOST /my-check-in.data (intent=draft|submit)cookie
Edit a past submissionPOST /my-check-in.data with a past cyclecookie
List / query past weeksGET /api/v1/cyclesbearer
Read a past submission's textGET /my-check-in.data?cycle=…cookie
Query summaries (run metadata)GET /api/v1/cycles/{id}/runsbearer
Read summary textNot exposed via API — app UI /home only
Ask AI about the orgPOST /ai/chat/streamcookie

📄 OpenAPI spec

A machine-readable OpenAPI 3.1 spec of everything above ships alongside this page: openapi.yaml. Import it into Postman, Insomnia, or any OpenAPI tool.