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.
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
- Read-only analytics
- Cycles, agent runs, people roster
- Auth: your
pca_/psk_key → JWT - Cannot write check-ins or call Ask AI
App API
- 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.
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
| Field | Value |
|---|---|
grant_typerequired | client_credentials |
client_idrequired | Your pca_… id |
client_secretrequired | Your 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.
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
| Param | Description |
|---|---|
page_token optional | Pagination 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
}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
| Param | Description |
|---|---|
cycleIdrequired | Cycle 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
}The org roster: everyone PulseCheck knows about, from the imported org chart.
Response fields
| Field | Type |
|---|---|
person_id | uuid |
name · work_email · title | string |
department · work_location | string · nullable |
employment_status | string |
curl 'https://api.pulsecheck.spectrocloud.com/api/v1/people' \ -H "Authorization: Bearer $TOKEN"
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
| Param | Description |
|---|---|
cyclerequired | Cycle (week) UUID. From GET /api/v1/cycles or the page URL. |
editrequired | Set 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>'
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
| Field | Description |
|---|---|
accomplishments | What you did. Rich-text HTML. |
upcoming | What's next. Plain text. |
blockers | Blockers. Rich-text HTML. |
intentrequired | draft 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.
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
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
| Approach | How | Trade-off |
|---|---|---|
| Reuse your Chrome profile | Playwright with your logged-in Chrome profile — SSO completes silently. | Zero credential handling. Chrome must not hold a profile lock. |
| One-time interactive login | Log in by hand once, persist storageState, replay headless. | Re-run login when the cookie expires (302 → /sign-in). |
| Fully headless fresh login | Script Okta username/password + factor. | Blocked by MFA (push/WebAuthn can't be scripted). |
| Ask for a write API token | Request 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… | Use | Auth |
|---|---|---|
| Create / draft / submit a check-in | POST /my-check-in.data (intent=draft|submit) | |
| Edit a past submission | POST /my-check-in.data with a past cycle | |
| List / query past weeks | GET /api/v1/cycles | bearer |
| Read a past submission's text | GET /my-check-in.data?cycle=… | |
| Query summaries (run metadata) | GET /api/v1/cycles/{id}/runs | bearer |
| Read summary text | Not exposed via API — app UI /home only | — |
| Ask AI about the org | POST /ai/chat/stream |
📄 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.